From 9f75fe7a36813c1f3bd1172cb0549cd519bf2db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 26 Mar 2026 17:37:50 +0300 Subject: [PATCH 01/75] merge prep --- .github/workflows/ci.yaml | 19 ++++++------ .npmrc | 2 +- Dockerfile | 8 ++--- Dockerfile.dev | 8 ++--- Dockerfile.rel | 6 ++-- README.md | 16 +++++----- docs/swagger.json | 2 +- docs/swagger.yaml | 2 +- logrotate.conf | 4 +-- package-lock.json | 14 ++++----- package.json | 30 ++++++++++++++----- scripts/cli-tests.js | 2 +- scripts/coverage.js | 2 +- scripts/init.js | 2 +- scripts/postinstall.js | 2 +- scripts/postmantest.js | 2 +- scripts/preuninstall.js | 2 +- scripts/run-test.js | 2 +- scripts/scripts-api.js | 2 +- scripts/start-dev.js | 2 +- scripts/start.js | 2 +- scripts/stop.js | 2 +- scripts/test.js | 2 +- scripts/util.js | 4 +-- src/cli/application.js | 2 +- src/cli/base-cli-handler.js | 6 ++-- src/cli/catalog.js | 2 +- src/cli/cli-data-types.js | 2 +- src/cli/config.js | 2 +- src/cli/controller.js | 2 +- src/cli/diagnostics.js | 2 +- src/cli/index.js | 2 +- src/cli/iofog.js | 2 +- src/cli/microservice.js | 2 +- src/cli/registry.js | 2 +- src/cli/start.js | 2 +- src/cli/tunnel.js | 2 +- src/config/config.yaml | 16 +++++----- src/config/index.js | 2 +- src/config/nats-system-rules.js | 2 +- src/config/rbac-system-roles.js | 26 ++++++++-------- src/config/telemetry.js | 2 +- src/controllers/agent-controller.js | 2 +- src/controllers/application-controller.js | 2 +- .../application-template-controller.js | 2 +- src/controllers/catalog-controller.js | 2 +- src/controllers/cluster-controller.js | 2 +- src/controllers/config-controller.js | 2 +- src/controllers/config-map-controller.js | 2 +- src/controllers/controller.js | 2 +- src/controllers/diagnostic-controller.js | 2 +- src/controllers/edge-resource-controller.js | 2 +- src/controllers/event-controller.js | 2 +- src/controllers/iofog-controller.js | 2 +- src/controllers/microservices-controller.js | 2 +- src/controllers/nats-controller.js | 2 +- src/controllers/rbac-controller.js | 2 +- src/controllers/registry-controller.js | 2 +- src/controllers/router-controller.js | 2 +- src/controllers/secret-controller.js | 2 +- src/controllers/service-controller.js | 2 +- src/controllers/tunnel-controller.js | 2 +- src/controllers/user-controller.js | 2 +- src/controllers/volume-mount-controller.js | 2 +- src/daemon.js | 2 +- src/data/managers/application-manager.js | 2 +- .../managers/application-template-manager.js | 2 +- .../application-template-variable-manager.js | 2 +- src/data/managers/base-manager.js | 2 +- .../managers/catalog-item-image-manager.js | 2 +- .../catalog-item-input-type-manager.js | 2 +- src/data/managers/catalog-item-manager.js | 2 +- .../catalog-item-output-type-manager.js | 2 +- src/data/managers/change-tracking-manager.js | 2 +- .../managers/cluster-controller-manager.js | 2 +- src/data/managers/config-manager.js | 2 +- src/data/managers/event-manager.js | 2 +- src/data/managers/fog-log-status-manager.js | 2 +- src/data/managers/fog-used-token-manager.js | 2 +- src/data/managers/hw-info-manager.js | 2 +- src/data/managers/iofog-manager.js | 2 +- .../managers/iofog-provision-key-manager.js | 2 +- src/data/managers/iofog-public-key-manager.js | 2 +- src/data/managers/iofog-type-manager.js | 2 +- .../managers/iofog-version-command-manager.js | 2 +- src/data/managers/microservice-arg-manager.js | 2 +- .../managers/microservice-cap-add-manager.js | 2 +- .../managers/microservice-cap-drop-manager.js | 2 +- .../microservice-cdi-device-manager.js | 2 +- src/data/managers/microservice-env-manager.js | 2 +- .../microservice-exec-status-manager.js | 2 +- .../microservice-extra-host-manager.js | 2 +- .../microservice-healthcheck-manager.js | 2 +- .../microservice-log-status-manager.js | 2 +- src/data/managers/microservice-manager.js | 2 +- .../managers/microservice-port-manager.js | 2 +- .../managers/microservice-status-manager.js | 2 +- .../managers/rbac-cache-version-manager.js | 2 +- .../managers/rbac-role-binding-manager.js | 2 +- src/data/managers/rbac-role-manager.js | 2 +- .../managers/rbac-service-account-manager.js | 2 +- .../managers/router-connection-manager.js | 2 +- src/data/managers/router-manager.js | 2 +- src/data/managers/service-manager.js | 2 +- .../managers/strace-diagnostics-manager.js | 2 +- src/data/managers/strace-manager.js | 2 +- src/data/managers/tags-manager.js | 2 +- src/data/managers/usb-info-manager.js | 2 +- src/data/managers/volume-mapping-manager.js | 2 +- src/data/managers/volume-mounting-manager.js | 2 +- src/data/models/changetracking.js | 2 +- .../seeders/mysql/db_seeder_mysql_v1.0.2.sql | 30 +++++++++---------- .../seeders/postgres/db_seeder_pg_v1.0.2.sql | 30 +++++++++---------- .../sqlite/db_seeder_sqlite_v1.0.2.sql | 30 +++++++++---------- src/decorators/authorization-decorator.js | 2 +- src/decorators/response-decorator.js | 2 +- src/decorators/transaction-decorator.js | 2 +- src/enums/fog-state.js | 2 +- src/enums/microservice-state.js | 2 +- src/helpers/app-helper.js | 2 +- src/helpers/constants.js | 2 +- src/helpers/error-messages.js | 2 +- src/helpers/errors.js | 2 +- src/helpers/system-naming.js | 2 +- src/init.js | 2 +- src/jobs/controller-cleanup-job.js | 2 +- src/jobs/controller-heartbeat-job.js | 2 +- src/jobs/event-cleanup-job.js | 2 +- src/jobs/fog-status-job.js | 2 +- src/jobs/fog-token-cleanup-job.js | 2 +- src/jobs/nats-reconcile-worker-job.js | 2 +- src/jobs/stopped-app-status-job.js | 2 +- src/keycloak.json | 4 +-- src/lib/rbac/authorizer.js | 2 +- src/lib/rbac/middleware.js | 2 +- src/logger/index.js | 2 +- src/main.js | 2 +- src/middlewares/event-audit-middleware.js | 2 +- src/routes/agent.js | 2 +- src/routes/application.js | 2 +- src/routes/applicationTemplate.js | 2 +- src/routes/capabilities.js | 2 +- src/routes/catalog.js | 2 +- src/routes/cluster.js | 2 +- src/routes/config.js | 2 +- src/routes/configMap.js | 2 +- src/routes/controller.js | 2 +- src/routes/diagnostics.js | 2 +- src/routes/edgeResource.js | 2 +- src/routes/event.js | 2 +- src/routes/flow.js | 2 +- src/routes/iofog.js | 2 +- src/routes/microservices.js | 2 +- src/routes/nats.js | 2 +- src/routes/rbac.js | 2 +- src/routes/registries.js | 2 +- src/routes/router.js | 2 +- src/routes/secret.js | 2 +- src/routes/service.js | 2 +- src/routes/tunnel.js | 2 +- src/routes/user.js | 2 +- src/routes/volumeMount.js | 2 +- src/schemas/agent.js | 2 +- src/schemas/catalog.js | 2 +- src/schemas/cluster-controller.js | 2 +- src/schemas/config.js | 2 +- src/schemas/controlPlane.js | 2 +- src/schemas/diagnostics.js | 2 +- src/schemas/edgeResource.js | 2 +- src/schemas/index.js | 2 +- src/schemas/iofog.js | 2 +- src/schemas/rbac.js | 2 +- src/schemas/registry.js | 2 +- src/schemas/tunnel.js | 2 +- src/schemas/user.js | 2 +- src/server.js | 8 ++--- src/services/agent-service.js | 4 +-- src/services/application-service.js | 2 +- src/services/application-template-service.js | 2 +- src/services/catalog-service.js | 12 ++++---- src/services/change-tracking-service.js | 2 +- src/services/cluster-controller-service.js | 2 +- src/services/config-map-service.js | 2 +- src/services/config-service.js | 2 +- src/services/controller-service.js | 4 +-- src/services/diagnostic-service.js | 2 +- src/services/edge-resource-service.js | 2 +- src/services/event-service.js | 2 +- src/services/iofog-key-service.js | 2 +- src/services/iofog-service.js | 4 +-- .../microservice-ports/microservice-port.js | 2 +- src/services/microservices-service.js | 2 +- src/services/nats-api-service.js | 2 +- src/services/nats-auth-service.js | 2 +- src/services/nats-hub-service.js | 2 +- src/services/nats-service.js | 2 +- src/services/rbac-service.js | 2 +- src/services/registry-service.js | 2 +- src/services/router-service.js | 6 ++-- src/services/secret-service.js | 2 +- src/services/services-service.js | 16 +++++----- src/services/tunnel-service.js | 2 +- src/services/user-service.js | 2 +- src/utils/ssl-utils.js | 2 +- src/vault/aws-secrets-manager-provider.js | 6 ++-- src/vault/azure-key-vault-provider.js | 4 +-- src/vault/base-vault-provider.js | 2 +- src/vault/google-secret-manager-provider.js | 4 +-- src/vault/hashicorp-vault-provider.js | 4 +-- src/vault/vault-manager.js | 6 ++-- src/websocket/log-session-manager.js | 2 +- swagger.js | 6 ++-- 212 files changed, 358 insertions(+), 343 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7710bea4..e5045857 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -47,10 +47,10 @@ jobs: - name: Replace values shell: bash env: - PAT: ${{ secrets.PAT }} + PAT: ${{ secrets.GITHUB_TOKEN }} run: | - sed -i.back "s|PAT|${PAT}|g" .npmrc - - run: npm config set @datasance:registry https://npm.pkg.github.com/ + sed -i.back "s|PAT|${GITHUB_TOKEN}|g" .npmrc + - run: npm config set @eclipse-iofog:registry https://npm.pkg.github.com/ - run: npm install --build-from-source --force - run: npm run standard - run: | @@ -83,10 +83,10 @@ jobs: - name: Replace values shell: bash env: - PAT: ${{ secrets.PAT }} + PAT: ${{ secrets.GITHUB_TOKEN }} run: | sed -i.back "s|PAT|${PAT}|g" .npmrc - - run: npm config set @datasance:registry https://npm.pkg.github.com/ + - run: npm config set @eclipse-iofog:registry https://npm.pkg.github.com/ - run: npm install --build-from-source --force - name: npm version @@ -138,7 +138,7 @@ jobs: with: registry: "ghcr.io" username: ${{ github.actor }} - password: ${{ secrets.PAT }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Build and Push to ghcr uses: docker/build-push-action@v3 @@ -149,8 +149,7 @@ jobs: platforms: linux/amd64, linux/arm64 push: true outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=Controller - build-args: GITHUB_TOKEN=${{ secrets.PAT }} tags: | - ghcr.io/datasance/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.VERSION }} - ghcr.io/datasance/${{ env.IMAGE_NAME }}:latest - ghcr.io/datasance/${{ env.IMAGE_NAME }}:main + ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.VERSION }} + ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:latest + ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:main diff --git a/.npmrc b/.npmrc index 11cf9fea..76740b5b 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,2 @@ //npm.pkg.github.com/:_authToken=PAT -@Datasance:registry=https://npm.pkg.github.com/ \ No newline at end of file +@Eclipse-iofog:registry=https://npm.pkg.github.com/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 023932a6..0abbef75 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,7 +44,7 @@ ENV NPM_CONFIG_PREFIX=/home/runner/.npm-global ENV NPM_CONFIG_CACHE=/home/runner/.npm ENV PATH=$PATH:/home/runner/.npm-global/bin -COPY --from=builder /tmp/datasance-iofogcontroller-*.tgz /home/runner/iofog-controller.tgz +COPY --from=builder /tmp/eclipse-iofog-iofogcontroller-*.tgz /home/runner/iofog-controller.tgz ENV PID_BASE=/home/runner @@ -52,10 +52,10 @@ RUN npm i -g /home/runner/iofog-controller.tgz && \ rm -rf /home/runner/iofog-controller.tgz && \ iofog-controller config dev-mode --on -RUN rm -rf /home/runner/.npm-global/lib/node_modules/@datasance/iofogcontroller/src/data/sqlite_files/* +RUN rm -rf /home/runner/.npm-global/lib/node_modules/@eclipse-iofog/iofogcontroller/src/data/sqlite_files/* COPY LICENSE /licenses/LICENSE LABEL org.opencontainers.image.description=controller -LABEL org.opencontainers.image.source=https://github.com/datasance/controller +LABEL org.opencontainers.image.source=https://github.com/eclipse-iofog/Controller LABEL org.opencontainers.image.licenses=EPL2.0 -CMD [ "node", "/home/runner/.npm-global/lib/node_modules/@datasance/iofogcontroller/src/server.js" ] +CMD [ "node", "/home/runner/.npm-global/lib/node_modules/@eclipse-iofog/iofogcontroller/src/server.js" ] diff --git a/Dockerfile.dev b/Dockerfile.dev index 36e060e8..ad4805dd 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -14,7 +14,7 @@ COPY . . # Set GitHub npm registry with authentication token RUN sed -i.back "s|PAT|${GITHUB_TOKEN}|g" .npmrc -RUN npm config set @datasance:registry https://npm.pkg.github.com/ +RUN npm config set @eclipse-iofog:registry https://npm.pkg.github.com/ RUN npm i --build-from-source --force @@ -36,13 +36,13 @@ RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python RUN python3 -m ensurepip RUN pip3 install --no-cache --upgrade pip setuptools -COPY --from=builder /tmp/datasance-iofogcontroller-*.tgz /tmp/iofog-controller.tgz +COPY --from=builder /tmp/eclipse-iofog-iofogcontroller-*.tgz /tmp/iofog-controller.tgz RUN npm i -g /tmp/iofog-controller.tgz && \ rm -rf /tmp/iofog-controller.tgz && \ iofog-controller config dev-mode --on LABEL org.opencontainers.image.description=controller -LABEL org.opencontainers.image.source=https://github.com/datasance/controller +LABEL org.opencontainers.image.source=https://github.com/eclipse-iofog/Controller LABEL org.opencontainers.image.licenses=EPL2.0 -CMD [ "node", "/usr/local/lib/node_modules/@datasance/iofogcontroller/src/server.js" ] +CMD [ "node", "/usr/local/lib/node_modules/@eclipse-iofog/iofogcontroller/src/server.js" ] diff --git a/Dockerfile.rel b/Dockerfile.rel index 62df6f6b..879d4ea8 100644 --- a/Dockerfile.rel +++ b/Dockerfile.rel @@ -31,13 +31,13 @@ RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python RUN python3 -m ensurepip RUN pip3 install --no-cache --upgrade pip setuptools LABEL org.opencontainers.image.description controller -LABEL org.opencontainers.image.source=https://github.com/datasance/controller +LABEL org.opencontainers.image.source=https://github.com/eclipse-iofog/Controller LABEL org.opencontainers.image.licenses=EPL2.0 -COPY --from=builder /tmp/datasance-iofogcontroller-*.tgz /tmp/iofog-controller.tgz +COPY --from=builder /tmp/eclipse-iofog-iofogcontroller-*.tgz /tmp/iofog-controller.tgz RUN npm i -g /tmp/iofog-controller.tgz && \ rm -rf /tmp/iofog-controller.tgz && \ iofog-controller config dev-mode --on -CMD [ "node", "/usr/local/lib/node_modules/@datasance/iofogcontroller/src/server.js" ] +CMD [ "node", "/usr/local/lib/node_modules/@eclipse-iofog/iofogcontroller/src/server.js" ] diff --git a/README.md b/README.md index da3e8d7c..f7abedb6 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ ### Status -![](https://img.shields.io/github/release/datasance/controller.svg?style=flat) +![](https://img.shields.io/github/release/eclipse-iofog/controller.svg?style=flat) -![](https://img.shields.io/github/repo-size/datasance/controller.svg?style=flat) -![](https://img.shields.io/github/last-commit/datasance/controller.svg?style=flat) -![](https://img.shields.io/github/contributors/datasance/controller.svg?style=flat) -![](https://img.shields.io/github/issues/datasance/controller.svg?style=flat) +![](https://img.shields.io/github/repo-size/eclipse-iofog/controller.svg?style=flat) +![](https://img.shields.io/github/last-commit/eclipse-iofog/controller.svg?style=flat) +![](https://img.shields.io/github/contributors/eclipse-iofog/controller.svg?style=flat) +![](https://img.shields.io/github/issues/eclipse-iofog/controller.svg?style=flat) ![Supports amd64 Architecture][amd64-shield] ![Supports aarch64 Architecture][arm64-shield] @@ -19,13 +19,13 @@ ## Install -The entire Datasance PoT platform is best deployed through the unified CLI: `potctl`. +The entire Eclipse ioFog platform is best deployed through the unified CLI: `iofogctl`. -Go to [Datasance Docs](https://docs.datasance.com) to learn how to deploy the ioFog Control Plane and Agents. +Go to [Eclipse ioFog Docs](https://docs.eclipse-iofog.com) to learn how to deploy the ioFog Control Plane and Agents. ## Usage ``` iofog-controller ``` -For full installation and usage, visit [Datasance Docs](https://docs.datasance.com). +For full installation and usage, visit [Eclipse ioFog Docs](https://docs.eclipse-iofog.com). diff --git a/docs/swagger.json b/docs/swagger.json index 16aac50d..a51ce187 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -2,7 +2,7 @@ "openapi" : "3.0.0", "info" : { "version" : "1.0.0", - "title" : "Datasance PoT Controller" + "title" : "Eclipse ioFog Controller" }, "tags" : [ { "name" : "Controller", diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 86d383d2..7d5518c5 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,7 +1,7 @@ openapi : "3.0.0" info: version: 3.7.0 - title: Datasance PoT Controller + title: Eclipse ioFog Controller paths: /status: get: diff --git a/logrotate.conf b/logrotate.conf index 9690627c..a7d220e3 100644 --- a/logrotate.conf +++ b/logrotate.conf @@ -10,8 +10,8 @@ postrotate if [ -f /home/runner/iofog-controller.pid ]; then kill -HUP `cat /home/runner/iofog-controller.pid`; - elif [ -f /opt/iofog/controller/lib/node_modules/@datasance/iofogcontroller/src/iofog-controller.pid ]; then - kill -HUP `cat /opt/iofog/controller/lib/node_modules/@datasance/iofogcontroller/src/iofog-controller.pid`; + elif [ -f /opt/iofog/controller/lib/node_modules/@eclipse-iofog/iofogcontroller/src/iofog-controller.pid ]; then + kill -HUP `cat /opt/iofog/controller/lib/node_modules/@eclipse-iofog/iofogcontroller/src/iofog-controller.pid`; fi endscript } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f386e142..27568a77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@datasance/iofogcontroller", + "name": "@eclipse-iofog/iofogcontroller", "version": "3.7.2", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@datasance/iofogcontroller", + "name": "@eclipse-iofog/iofogcontroller", "version": "3.7.2", "hasInstallScript": true, "license": "EPL-2.0", @@ -13,7 +13,7 @@ "@aws-sdk/client-secrets-manager": "^3.1015.0", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", - "@datasance/ecn-viewer": "1.4.3", + "@eclipse-iofog/ecn-viewer": "3.7.1", "@google-cloud/secret-manager": "^6.1.1", "@kubernetes/client-node": "^1.4.0", "@msgpack/msgpack": "^3.1.2", @@ -1304,10 +1304,10 @@ "node": ">=0.1.90" } }, - "node_modules/@datasance/ecn-viewer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@datasance/ecn-viewer/-/ecn-viewer-1.4.3.tgz", - "integrity": "sha512-1pMr5dfIB6CaQYyL3E/XgBTG8XcJ2wVsNhAmDu6JtvGmaBbAjpuKFSLVkjNh3cBCFDGLmRQ61YQ+Yi7xzoxalw==", + "node_modules/@eclipse-iofog/ecn-viewer": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@eclipse-iofog/ecn-viewer/-/ecn-viewer-3.7.1.tgz", + "integrity": "sha512-dTPP/B02C7ImyCBE3sU3XqNLhv9GRU3EKtwhxEOPjx+qMRiKXji5pNJ35rtBEOIUkOPbubhSWwbtM17LLmtxAQ==", "license": "EPL-2.0" }, "node_modules/@eslint-community/eslint-utils": { diff --git a/package.json b/package.json index af117deb..94ba242f 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,26 @@ { - "name": "@datasance/iofogcontroller", + "name": "@eclipse-iofog/iofogcontroller", "version": "3.7.2", - "description": "ioFog Controller project for Datasance PoT @ datasance.com \\nCopyright (c) 2023 Datasance Teknoloji A.S.", + "description": "ioFog Controller project for Eclipse ioFog @ iofog.org \\nCopyright (c) 2023 Contributors to the Eclipse ioFog Project", "main": "./src/main.js", - "author": "Emirhan Durmus", + "author": "Eclipse ioFog Project", "contributors": [ + "Kilton Hopkins ", + "Saeid Rezaei Baghbidi", + "Alexandre de Wergifosse", + "Pavel Kazlou", + "Egor Krylovich", + "Iryna Laryionava", + "Maryna Lipnitskaya", + "Dmitriy Kudasov", + "Dmitry Stolbunov", + "Darya Busel", + "Alexander Shpak", + "Kate Lukashick", + "Eugene Pankov", + "Maksim Chepelev", + "Tetiana Yatsiuk", + "Sergey Valevich", "Emirhan Durmus ", "Alpaslan Doğan " ], @@ -13,7 +29,7 @@ "node": "^24.0.0" }, "bugs": { - "email": "support@datasance.com" + "email": "edgemaster@iofog.org" }, "standard": { "ignore": [ @@ -22,10 +38,10 @@ "src/utils/k8s-client.js" ] }, - "homepage": "https://www.datasance.com", + "homepage": "https://www.iofog.org", "repository": { "type": "git", - "url": "https://github.com/Datasance/Controller" + "url": "https://github.com/eclipse-iofog/Controller" }, "publishConfig": { "registry": "https://npm.pkg.github.com/" @@ -63,7 +79,7 @@ "@aws-sdk/client-secrets-manager": "^3.1015.0", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", - "@datasance/ecn-viewer": "1.4.3", + "@eclipse-iofog/ecn-viewer": "3.7.1", "@google-cloud/secret-manager": "^6.1.1", "@kubernetes/client-node": "^1.4.0", "@msgpack/msgpack": "^3.1.2", diff --git a/scripts/cli-tests.js b/scripts/cli-tests.js index e64f86f3..10eff9e4 100644 --- a/scripts/cli-tests.js +++ b/scripts/cli-tests.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/coverage.js b/scripts/coverage.js index ee8c0d20..77098ce7 100644 --- a/scripts/coverage.js +++ b/scripts/coverage.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/init.js b/scripts/init.js index 09cbee6b..1bcd23d1 100644 --- a/scripts/init.js +++ b/scripts/init.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 851c0062..ff87e5d2 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/postmantest.js b/scripts/postmantest.js index 555254a3..ce62ef6a 100644 --- a/scripts/postmantest.js +++ b/scripts/postmantest.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/preuninstall.js b/scripts/preuninstall.js index b22fd9e2..fa94b698 100644 --- a/scripts/preuninstall.js +++ b/scripts/preuninstall.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/run-test.js b/scripts/run-test.js index e6eb8230..ac03564c 100644 --- a/scripts/run-test.js +++ b/scripts/run-test.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/scripts-api.js b/scripts/scripts-api.js index 1af3818c..1aaaf700 100644 --- a/scripts/scripts-api.js +++ b/scripts/scripts-api.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/start-dev.js b/scripts/start-dev.js index 996af7f9..47019c9f 100644 --- a/scripts/start-dev.js +++ b/scripts/start-dev.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/start.js b/scripts/start.js index bf021f27..b4b37946 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/stop.js b/scripts/stop.js index bcbc4e8d..d116245e 100644 --- a/scripts/stop.js +++ b/scripts/stop.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/test.js b/scripts/test.js index 3236bbd3..e7b96aa2 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/util.js b/scripts/util.js index 0245dd7d..00dd1b1c 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -22,7 +22,7 @@ const DEV_DB_BACKUP = `${TEMP_DIR}/dev_database.sqlite` const PROD_DB = `${ROOT_DIR}/src/data/sqlite_files/prod_database.sqlite` const PROD_DB_BACKUP = `${TEMP_DIR}/prod_database.sqlite` -let dbName = process.env.DB_NAME || 'pot-controller' +let dbName = process.env.DB_NAME || 'iofog-controller' if (!dbName.endsWith('.sqlite')) { dbName += '.sqlite' } diff --git a/src/cli/application.js b/src/cli/application.js index e5997dd8..f1504b04 100644 --- a/src/cli/application.js +++ b/src/cli/application.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/cli/base-cli-handler.js b/src/cli/base-cli-handler.js index 48b0bdb3..ac5fbc87 100644 --- a/src/cli/base-cli-handler.js +++ b/src/cli/base-cli-handler.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -62,7 +62,7 @@ class CLIHandler { const usage = [ { header: 'ioFogController', - content: 'Fog Controller project for Eclipse IoFog @ iofog.org \\nCopyright (c) 2023 Datasance Teknoloji A.S.' + content: 'Fog Controller project for Eclipse IoFog @ iofog.org \\nCopyright (c) 2023 Contributors to the Eclipse ioFog Project' } ].concat(sections) logger.cliRes(commandLineUsage(usage)) @@ -95,7 +95,7 @@ class CLIHandler { const usage = [ { header: 'ioFogController', - content: 'Fog Controller project for Eclipse IoFog @ iofog.org \\nCopyright (c) 2023 Datasance Teknoloji A.S.' + content: 'Fog Controller project for Eclipse IoFog @ iofog.org \\nCopyright (c) 2023 Contributors to the Eclipse ioFog Project' } ].concat(sections) logger.cliRes(commandLineUsage(usage)) diff --git a/src/cli/catalog.js b/src/cli/catalog.js index 9f15b451..c7154c06 100644 --- a/src/cli/catalog.js +++ b/src/cli/catalog.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/cli/cli-data-types.js b/src/cli/cli-data-types.js index 909e3c11..0fe072cc 100644 --- a/src/cli/cli-data-types.js +++ b/src/cli/cli-data-types.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/cli/config.js b/src/cli/config.js index 6492c4b0..b1027279 100644 --- a/src/cli/config.js +++ b/src/cli/config.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/cli/controller.js b/src/cli/controller.js index 9ae8561c..cc8cbf08 100644 --- a/src/cli/controller.js +++ b/src/cli/controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/cli/diagnostics.js b/src/cli/diagnostics.js index 165c55ed..1e362950 100644 --- a/src/cli/diagnostics.js +++ b/src/cli/diagnostics.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/cli/index.js b/src/cli/index.js index a905bf1d..2ceb2d8e 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/cli/iofog.js b/src/cli/iofog.js index 8ea90aae..8a63dbbb 100644 --- a/src/cli/iofog.js +++ b/src/cli/iofog.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/cli/microservice.js b/src/cli/microservice.js index 59a7002b..05953117 100644 --- a/src/cli/microservice.js +++ b/src/cli/microservice.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/cli/registry.js b/src/cli/registry.js index 1efee349..a9938117 100644 --- a/src/cli/registry.js +++ b/src/cli/registry.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/cli/start.js b/src/cli/start.js index 0b169502..0d517fc9 100644 --- a/src/cli/start.js +++ b/src/cli/start.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/cli/tunnel.js b/src/cli/tunnel.js index 75efe9ff..ce8816fc 100644 --- a/src/cli/tunnel.js +++ b/src/cli/tunnel.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/config/config.yaml b/src/config/config.yaml index b6388415..b3df159b 100644 --- a/src/config/config.yaml +++ b/src/config/config.yaml @@ -1,9 +1,9 @@ # Application Configuration app: - name: pot # Application name + name: iofog # Application name uuid: "" controlPlane: Remote # Control plane type: Remote or Kubernetes or Local - namespace: datasance # Namespace for the application + namespace: iofog # Namespace for the application # Server Configuration server: @@ -111,14 +111,14 @@ bridgePorts: # System Images Configuration systemImages: router: - "1": "ghcr.io/datasance/router:latest" - "2": "ghcr.io/datasance/router:latest" + "1": "ghcr.io/eclipse-iofog/router:latest" + "2": "ghcr.io/eclipse-iofog/router:latest" debug: - "1": "ghcr.io/datasance/node-debugger:latest" - "2": "ghcr.io/datasance/node-debugger:latest" + "1": "ghcr.io/eclipse-iofog/node-debugger:latest" + "2": "ghcr.io/eclipse-iofog/node-debugger:latest" nats: - "1": "ghcr.io/datasance/nats:latest" - "2": "ghcr.io/datasance/nats:latest" + "1": "ghcr.io/eclipse-iofog/nats:latest" + "2": "ghcr.io/eclipse-iofog/nats:latest" # NATS Configuration nats: diff --git a/src/config/index.js b/src/config/index.js index 267697a4..66c21044 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/config/nats-system-rules.js b/src/config/nats-system-rules.js index 8bcd63e0..e093f91a 100644 --- a/src/config/nats-system-rules.js +++ b/src/config/nats-system-rules.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/config/rbac-system-roles.js b/src/config/rbac-system-roles.js index cbbc24fa..0b9efca2 100644 --- a/src/config/rbac-system-roles.js +++ b/src/config/rbac-system-roles.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -19,13 +19,13 @@ const config = require('./index') function getNamespace () { - return process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'datasance') + return process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'iofog') } module.exports = { ADMIN_ROLE: { name: 'admin', - apiVersion: 'datasance.com/v3', + apiVersion: 'iofog.org/v3', kind: 'Role', get namespace () { return getNamespace() @@ -40,7 +40,7 @@ module.exports = { }, SRE_ROLE: { name: 'sre', - apiVersion: 'datasance.com/v3', + apiVersion: 'iofog.org/v3', kind: 'Role', get namespace () { return getNamespace() @@ -60,7 +60,7 @@ module.exports = { }, DEVELOPER_ROLE: { name: 'developer', - apiVersion: 'datasance.com/v3', + apiVersion: 'iofog.org/v3', kind: 'Role', get namespace () { return getNamespace() @@ -80,7 +80,7 @@ module.exports = { }, VIEWER_ROLE: { name: 'viewer', - apiVersion: 'datasance.com/v3', + apiVersion: 'iofog.org/v3', kind: 'Role', get namespace () { return getNamespace() @@ -95,7 +95,7 @@ module.exports = { }, AGENT_ADMIN_ROLE: { name: 'agent-admin', - apiVersion: 'agent.datasance.com/v3', + apiVersion: 'agent.iofog.org/v3', kind: 'Role', get namespace () { return getNamespace() @@ -111,7 +111,7 @@ module.exports = { // - deprovision (delete) // - config (post) // - prune (post) - apiGroups: ['agent.datasance.com/v3'], + apiGroups: ['agent.iofog.org/v3'], resources: ['*'], verbs: ['*'] } @@ -119,29 +119,29 @@ module.exports = { }, MICROSERVICE_ROLE: { name: 'microservice', - apiVersion: 'agent.datasance.com/v3', + apiVersion: 'agent.iofog.org/v3', kind: 'Role', get namespace () { return getNamespace() }, rules: [ { - apiGroups: ['agent.datasance.com/v3'], + apiGroups: ['agent.iofog.org/v3'], resources: ['gps'], verbs: ['get', 'patch'] }, { - apiGroups: ['agent.datasance.com/v3'], + apiGroups: ['agent.iofog.org/v3'], resources: ['config'], verbs: ['get'] }, { - apiGroups: ['agent.datasance.com/v3'], + apiGroups: ['agent.iofog.org/v3'], resources: ['log'], verbs: ['post'] }, { - apiGroups: ['agent.datasance.com/v3'], + apiGroups: ['agent.iofog.org/v3'], resources: ['control'], verbs: ['get'] // Note: WebSocket 'get' for control is handled separately by agent diff --git a/src/config/telemetry.js b/src/config/telemetry.js index dd969184..9ecd47e9 100644 --- a/src/config/telemetry.js +++ b/src/config/telemetry.js @@ -25,7 +25,7 @@ function awaitAttributes (detector) { // Initialize OpenTelemetry const sdk = new NodeSDK({ - serviceName: process.env.OTEL_SERVICE_NAME || 'pot-controller', + serviceName: process.env.OTEL_SERVICE_NAME || 'iofog-controller', resource: new Resource({}), resourceDetectors: [ awaitAttributes(envDetectorSync), diff --git a/src/controllers/agent-controller.js b/src/controllers/agent-controller.js index e1aff0c2..0a1ec349 100644 --- a/src/controllers/agent-controller.js +++ b/src/controllers/agent-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/application-controller.js b/src/controllers/application-controller.js index dc362314..477018db 100644 --- a/src/controllers/application-controller.js +++ b/src/controllers/application-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/application-template-controller.js b/src/controllers/application-template-controller.js index 61f28111..3aa05f34 100644 --- a/src/controllers/application-template-controller.js +++ b/src/controllers/application-template-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/catalog-controller.js b/src/controllers/catalog-controller.js index dcd35cd5..1c04382a 100644 --- a/src/controllers/catalog-controller.js +++ b/src/controllers/catalog-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/cluster-controller.js b/src/controllers/cluster-controller.js index e937d11b..585d3031 100644 --- a/src/controllers/cluster-controller.js +++ b/src/controllers/cluster-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/config-controller.js b/src/controllers/config-controller.js index 2b5efd72..e08b5cf0 100644 --- a/src/controllers/config-controller.js +++ b/src/controllers/config-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/config-map-controller.js b/src/controllers/config-map-controller.js index ec79358e..98993603 100644 --- a/src/controllers/config-map-controller.js +++ b/src/controllers/config-map-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/controller.js b/src/controllers/controller.js index abd4650e..d7399da4 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/diagnostic-controller.js b/src/controllers/diagnostic-controller.js index 35d053be..05eec963 100644 --- a/src/controllers/diagnostic-controller.js +++ b/src/controllers/diagnostic-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/edge-resource-controller.js b/src/controllers/edge-resource-controller.js index 68ffb560..e8213356 100644 --- a/src/controllers/edge-resource-controller.js +++ b/src/controllers/edge-resource-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/event-controller.js b/src/controllers/event-controller.js index 62f49107..983d15a3 100644 --- a/src/controllers/event-controller.js +++ b/src/controllers/event-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 diff --git a/src/controllers/iofog-controller.js b/src/controllers/iofog-controller.js index a89950f2..a8cc6b0e 100644 --- a/src/controllers/iofog-controller.js +++ b/src/controllers/iofog-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/microservices-controller.js b/src/controllers/microservices-controller.js index daf1659b..1a739476 100644 --- a/src/controllers/microservices-controller.js +++ b/src/controllers/microservices-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/nats-controller.js b/src/controllers/nats-controller.js index 3b55605f..794cd3f9 100644 --- a/src/controllers/nats-controller.js +++ b/src/controllers/nats-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/rbac-controller.js b/src/controllers/rbac-controller.js index fbfe25b8..a182199f 100644 --- a/src/controllers/rbac-controller.js +++ b/src/controllers/rbac-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/registry-controller.js b/src/controllers/registry-controller.js index 84b240e6..9472625f 100644 --- a/src/controllers/registry-controller.js +++ b/src/controllers/registry-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/router-controller.js b/src/controllers/router-controller.js index 7c373414..08d9014c 100644 --- a/src/controllers/router-controller.js +++ b/src/controllers/router-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/secret-controller.js b/src/controllers/secret-controller.js index d4457a06..1e9a3c39 100644 --- a/src/controllers/secret-controller.js +++ b/src/controllers/secret-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/service-controller.js b/src/controllers/service-controller.js index b07df17a..c1ec5b63 100644 --- a/src/controllers/service-controller.js +++ b/src/controllers/service-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/tunnel-controller.js b/src/controllers/tunnel-controller.js index fb3ea34c..a04b1b55 100644 --- a/src/controllers/tunnel-controller.js +++ b/src/controllers/tunnel-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/user-controller.js b/src/controllers/user-controller.js index 9cff7744..72450b49 100644 --- a/src/controllers/user-controller.js +++ b/src/controllers/user-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/controllers/volume-mount-controller.js b/src/controllers/volume-mount-controller.js index 9628a8ba..f070abe3 100644 --- a/src/controllers/volume-mount-controller.js +++ b/src/controllers/volume-mount-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/daemon.js b/src/daemon.js index 105ff882..e31e834d 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/application-manager.js b/src/data/managers/application-manager.js index 3b546e00..e27efea9 100644 --- a/src/data/managers/application-manager.js +++ b/src/data/managers/application-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/application-template-manager.js b/src/data/managers/application-template-manager.js index 98ef4ca0..8e332f0a 100644 --- a/src/data/managers/application-template-manager.js +++ b/src/data/managers/application-template-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/application-template-variable-manager.js b/src/data/managers/application-template-variable-manager.js index 66fae8f2..0a583087 100644 --- a/src/data/managers/application-template-variable-manager.js +++ b/src/data/managers/application-template-variable-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/base-manager.js b/src/data/managers/base-manager.js index 4dc4ed24..3ad85151 100644 --- a/src/data/managers/base-manager.js +++ b/src/data/managers/base-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* -* * Copyright (c) 2023 Datasance Teknoloji A.S. +* * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/catalog-item-image-manager.js b/src/data/managers/catalog-item-image-manager.js index 14076f1c..4315b30e 100644 --- a/src/data/managers/catalog-item-image-manager.js +++ b/src/data/managers/catalog-item-image-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/catalog-item-input-type-manager.js b/src/data/managers/catalog-item-input-type-manager.js index 2aa19d81..743e0c3f 100644 --- a/src/data/managers/catalog-item-input-type-manager.js +++ b/src/data/managers/catalog-item-input-type-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/catalog-item-manager.js b/src/data/managers/catalog-item-manager.js index c190fffe..b901880d 100644 --- a/src/data/managers/catalog-item-manager.js +++ b/src/data/managers/catalog-item-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/catalog-item-output-type-manager.js b/src/data/managers/catalog-item-output-type-manager.js index 6ec69012..f4d24074 100644 --- a/src/data/managers/catalog-item-output-type-manager.js +++ b/src/data/managers/catalog-item-output-type-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/change-tracking-manager.js b/src/data/managers/change-tracking-manager.js index 1f74c4c9..2bc8a7c8 100644 --- a/src/data/managers/change-tracking-manager.js +++ b/src/data/managers/change-tracking-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/cluster-controller-manager.js b/src/data/managers/cluster-controller-manager.js index 7b7d767c..9509edd0 100644 --- a/src/data/managers/cluster-controller-manager.js +++ b/src/data/managers/cluster-controller-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/config-manager.js b/src/data/managers/config-manager.js index 824f2340..9037f171 100644 --- a/src/data/managers/config-manager.js +++ b/src/data/managers/config-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/event-manager.js b/src/data/managers/event-manager.js index 7b32bdff..f5e5770a 100644 --- a/src/data/managers/event-manager.js +++ b/src/data/managers/event-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 diff --git a/src/data/managers/fog-log-status-manager.js b/src/data/managers/fog-log-status-manager.js index 34f3c28f..9a694686 100644 --- a/src/data/managers/fog-log-status-manager.js +++ b/src/data/managers/fog-log-status-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/fog-used-token-manager.js b/src/data/managers/fog-used-token-manager.js index 0b9a3349..a12f72eb 100644 --- a/src/data/managers/fog-used-token-manager.js +++ b/src/data/managers/fog-used-token-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/hw-info-manager.js b/src/data/managers/hw-info-manager.js index 9a8fcadb..f68fd611 100644 --- a/src/data/managers/hw-info-manager.js +++ b/src/data/managers/hw-info-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/iofog-manager.js b/src/data/managers/iofog-manager.js index a0e98c76..c4a541a3 100644 --- a/src/data/managers/iofog-manager.js +++ b/src/data/managers/iofog-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/iofog-provision-key-manager.js b/src/data/managers/iofog-provision-key-manager.js index 0e6e1ca4..bf8820f8 100644 --- a/src/data/managers/iofog-provision-key-manager.js +++ b/src/data/managers/iofog-provision-key-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/iofog-public-key-manager.js b/src/data/managers/iofog-public-key-manager.js index 29c8dc35..9615190c 100644 --- a/src/data/managers/iofog-public-key-manager.js +++ b/src/data/managers/iofog-public-key-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/iofog-type-manager.js b/src/data/managers/iofog-type-manager.js index 1111bf6b..9262867f 100644 --- a/src/data/managers/iofog-type-manager.js +++ b/src/data/managers/iofog-type-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/iofog-version-command-manager.js b/src/data/managers/iofog-version-command-manager.js index db027a5b..6b7d4cc4 100644 --- a/src/data/managers/iofog-version-command-manager.js +++ b/src/data/managers/iofog-version-command-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/microservice-arg-manager.js b/src/data/managers/microservice-arg-manager.js index 5ccf6343..8a97f19a 100644 --- a/src/data/managers/microservice-arg-manager.js +++ b/src/data/managers/microservice-arg-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/microservice-cap-add-manager.js b/src/data/managers/microservice-cap-add-manager.js index b92430fa..a0e27937 100644 --- a/src/data/managers/microservice-cap-add-manager.js +++ b/src/data/managers/microservice-cap-add-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/microservice-cap-drop-manager.js b/src/data/managers/microservice-cap-drop-manager.js index 2dce889f..d64d8b05 100644 --- a/src/data/managers/microservice-cap-drop-manager.js +++ b/src/data/managers/microservice-cap-drop-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/microservice-cdi-device-manager.js b/src/data/managers/microservice-cdi-device-manager.js index dc13ab60..89e49d66 100644 --- a/src/data/managers/microservice-cdi-device-manager.js +++ b/src/data/managers/microservice-cdi-device-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/microservice-env-manager.js b/src/data/managers/microservice-env-manager.js index a9ffae34..23f9cfef 100644 --- a/src/data/managers/microservice-env-manager.js +++ b/src/data/managers/microservice-env-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/microservice-exec-status-manager.js b/src/data/managers/microservice-exec-status-manager.js index ff5edec7..4c6a73f8 100644 --- a/src/data/managers/microservice-exec-status-manager.js +++ b/src/data/managers/microservice-exec-status-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/microservice-extra-host-manager.js b/src/data/managers/microservice-extra-host-manager.js index 22e0a340..d9eb79d3 100644 --- a/src/data/managers/microservice-extra-host-manager.js +++ b/src/data/managers/microservice-extra-host-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/microservice-healthcheck-manager.js b/src/data/managers/microservice-healthcheck-manager.js index 9d905bcd..08afd413 100644 --- a/src/data/managers/microservice-healthcheck-manager.js +++ b/src/data/managers/microservice-healthcheck-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/microservice-log-status-manager.js b/src/data/managers/microservice-log-status-manager.js index deaa3ff6..f63c0985 100644 --- a/src/data/managers/microservice-log-status-manager.js +++ b/src/data/managers/microservice-log-status-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/microservice-manager.js b/src/data/managers/microservice-manager.js index c39d1a44..2b698e08 100644 --- a/src/data/managers/microservice-manager.js +++ b/src/data/managers/microservice-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/microservice-port-manager.js b/src/data/managers/microservice-port-manager.js index 84c7c22f..49b7755a 100644 --- a/src/data/managers/microservice-port-manager.js +++ b/src/data/managers/microservice-port-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/microservice-status-manager.js b/src/data/managers/microservice-status-manager.js index 9690e21d..8cfead3b 100644 --- a/src/data/managers/microservice-status-manager.js +++ b/src/data/managers/microservice-status-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/rbac-cache-version-manager.js b/src/data/managers/rbac-cache-version-manager.js index 73666a65..bae1df70 100644 --- a/src/data/managers/rbac-cache-version-manager.js +++ b/src/data/managers/rbac-cache-version-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/rbac-role-binding-manager.js b/src/data/managers/rbac-role-binding-manager.js index a04c1c49..eff9f32f 100644 --- a/src/data/managers/rbac-role-binding-manager.js +++ b/src/data/managers/rbac-role-binding-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/rbac-role-manager.js b/src/data/managers/rbac-role-manager.js index 43ee4191..2c76b2b9 100644 --- a/src/data/managers/rbac-role-manager.js +++ b/src/data/managers/rbac-role-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/rbac-service-account-manager.js b/src/data/managers/rbac-service-account-manager.js index c7ce0294..fa04f2ac 100644 --- a/src/data/managers/rbac-service-account-manager.js +++ b/src/data/managers/rbac-service-account-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/router-connection-manager.js b/src/data/managers/router-connection-manager.js index 57108ff4..99c50418 100644 --- a/src/data/managers/router-connection-manager.js +++ b/src/data/managers/router-connection-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/router-manager.js b/src/data/managers/router-manager.js index 645d7b4d..d297333d 100644 --- a/src/data/managers/router-manager.js +++ b/src/data/managers/router-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/service-manager.js b/src/data/managers/service-manager.js index 9942b36a..d1ca3e01 100644 --- a/src/data/managers/service-manager.js +++ b/src/data/managers/service-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/strace-diagnostics-manager.js b/src/data/managers/strace-diagnostics-manager.js index 9b2402ba..2bebf9cb 100644 --- a/src/data/managers/strace-diagnostics-manager.js +++ b/src/data/managers/strace-diagnostics-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/strace-manager.js b/src/data/managers/strace-manager.js index 577cbac1..5fc6b4e9 100644 --- a/src/data/managers/strace-manager.js +++ b/src/data/managers/strace-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/tags-manager.js b/src/data/managers/tags-manager.js index ebde21a1..f7095a31 100644 --- a/src/data/managers/tags-manager.js +++ b/src/data/managers/tags-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/usb-info-manager.js b/src/data/managers/usb-info-manager.js index 85f3a423..aa57c74e 100644 --- a/src/data/managers/usb-info-manager.js +++ b/src/data/managers/usb-info-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/volume-mapping-manager.js b/src/data/managers/volume-mapping-manager.js index a9e77ff7..b57f4239 100644 --- a/src/data/managers/volume-mapping-manager.js +++ b/src/data/managers/volume-mapping-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/managers/volume-mounting-manager.js b/src/data/managers/volume-mounting-manager.js index b4f44384..7538825d 100644 --- a/src/data/managers/volume-mounting-manager.js +++ b/src/data/managers/volume-mounting-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/models/changetracking.js b/src/data/models/changetracking.js index 77af5d07..126a3fad 100644 --- a/src/data/models/changetracking.js +++ b/src/data/models/changetracking.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/data/seeders/mysql/db_seeder_mysql_v1.0.2.sql b/src/data/seeders/mysql/db_seeder_mysql_v1.0.2.sql index d20573e2..3c984e15 100644 --- a/src/data/seeders/mysql/db_seeder_mysql_v1.0.2.sql +++ b/src/data/seeders/mysql/db_seeder_mysql_v1.0.2.sql @@ -7,11 +7,11 @@ VALUES INSERT INTO `CatalogItems` (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) VALUES - ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('Debug', 'The built-in debugger for Datasance PoT IoFog Agent.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + ('Router', 'The built-in router for Eclipse ioFog.', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, false, 1), + ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, true, 1), + ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, true, 1), + ('Debug', 'The built-in debugger for Eclipse ioFog Agent.', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Eclipse ioFog', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, true, 1); INSERT INTO `FogTypes` (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) VALUES @@ -25,16 +25,16 @@ WHERE fog_type_id IS NULL; INSERT INTO `CatalogItemImages` (catalog_item_id, fog_type_id, container_image) VALUES - (1, 1, 'ghcr.io/datasance/router:latest'), - (1, 2, 'ghcr.io/datasance/router:latest'), - (2, 1, 'ghcr.io/datasance/restblue:latest'), - (2, 2, 'ghcr.io/datasance/restblue:latest'), - (3, 1, 'ghcr.io/datasance/hal:latest'), - (3, 2, 'ghcr.io/datasance/hal:latest'), - (4, 1, 'ghcr.io/datasance/node-debugger:latest'), - (4, 2, 'ghcr.io/datasance/node-debugger:latest'), - (5, 1, 'ghcr.io/datasance/nats:latest'), - (5, 2, 'ghcr.io/datasance/nats:latest'); + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'); COMMIT; \ No newline at end of file diff --git a/src/data/seeders/postgres/db_seeder_pg_v1.0.2.sql b/src/data/seeders/postgres/db_seeder_pg_v1.0.2.sql index 8efec984..3ce268ee 100644 --- a/src/data/seeders/postgres/db_seeder_pg_v1.0.2.sql +++ b/src/data/seeders/postgres/db_seeder_pg_v1.0.2.sql @@ -7,11 +7,11 @@ VALUES INSERT INTO "CatalogItems" (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) VALUES - ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('Debug', 'The built-in debugger for Datasance PoT IoFog Agent.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + ('Router', 'The built-in router for Eclipse ioFog.', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, false, 1), + ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, true, 1), + ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, true, 1), + ('Debug', 'The built-in debugger for Eclipse ioFog Agent.', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Eclipse ioFog', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, true, 1); INSERT INTO "FogTypes" (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) VALUES @@ -25,15 +25,15 @@ WHERE fog_type_id IS NULL; INSERT INTO "CatalogItemImages" (catalog_item_id, fog_type_id, container_image) VALUES - (1, 1, 'ghcr.io/datasance/router:latest'), - (1, 2, 'ghcr.io/datasance/router:latest'), - (2, 1, 'ghcr.io/datasance/restblue:latest'), - (2, 2, 'ghcr.io/datasance/restblue:latest'), - (3, 1, 'ghcr.io/datasance/hal:latest'), - (3, 2, 'ghcr.io/datasance/hal:latest'), - (4, 1, 'ghcr.io/datasance/node-debugger:latest'), - (4, 2, 'ghcr.io/datasance/node-debugger:latest'), - (5, 1, 'ghcr.io/datasance/nats:latest'), - (5, 2, 'ghcr.io/datasance/nats:latest'); + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'); COMMIT; \ No newline at end of file diff --git a/src/data/seeders/sqlite/db_seeder_sqlite_v1.0.2.sql b/src/data/seeders/sqlite/db_seeder_sqlite_v1.0.2.sql index 1f5b1039..f4a837b0 100644 --- a/src/data/seeders/sqlite/db_seeder_sqlite_v1.0.2.sql +++ b/src/data/seeders/sqlite/db_seeder_sqlite_v1.0.2.sql @@ -5,11 +5,11 @@ VALUES INSERT INTO `CatalogItems` (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) VALUES - ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('Debug', 'The built-in debugger for Datasance PoT IoFog Agent.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + ('Router', 'The built-in router for Eclipse ioFog.', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, false, 1), + ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, true, 1), + ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, true, 1), + ('Debug', 'The built-in debugger for Eclipse ioFog Agent.', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Eclipse ioFog', 'SYSTEM', 'Eclipse ioFog', 0, 0, 'none.png', NULL, true, 1); INSERT INTO `FogTypes` (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) VALUES @@ -23,13 +23,13 @@ WHERE fog_type_id IS NULL; INSERT INTO `CatalogItemImages` (catalog_item_id, fog_type_id, container_image) VALUES - (1, 1, 'ghcr.io/datasance/router:latest'), - (1, 2, 'ghcr.io/datasance/router:latest'), - (2, 1, 'ghcr.io/datasance/restblue:latest'), - (2, 2, 'ghcr.io/datasance/restblue:latest'), - (3, 1, 'ghcr.io/datasance/hal:latest'), - (3, 2, 'ghcr.io/datasance/hal:latest'), - (4, 1, 'ghcr.io/datasance/node-debugger:latest'), - (4, 2, 'ghcr.io/datasance/node-debugger:latest'), - (5, 1, 'ghcr.io/datasance/nats:latest'), - (5, 2, 'ghcr.io/datasance/nats:latest'); + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'); diff --git a/src/decorators/authorization-decorator.js b/src/decorators/authorization-decorator.js index 9003b0b6..3a020f6a 100644 --- a/src/decorators/authorization-decorator.js +++ b/src/decorators/authorization-decorator.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/decorators/response-decorator.js b/src/decorators/response-decorator.js index a2c7d461..22027c3f 100644 --- a/src/decorators/response-decorator.js +++ b/src/decorators/response-decorator.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/decorators/transaction-decorator.js b/src/decorators/transaction-decorator.js index a1acea95..9f82b5d6 100644 --- a/src/decorators/transaction-decorator.js +++ b/src/decorators/transaction-decorator.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/enums/fog-state.js b/src/enums/fog-state.js index 2c7d712a..5542bc17 100644 --- a/src/enums/fog-state.js +++ b/src/enums/fog-state.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/enums/microservice-state.js b/src/enums/microservice-state.js index ac985f44..3b6ef65e 100644 --- a/src/enums/microservice-state.js +++ b/src/enums/microservice-state.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/helpers/app-helper.js b/src/helpers/app-helper.js index 070b97e0..24b16226 100644 --- a/src/helpers/app-helper.js +++ b/src/helpers/app-helper.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/helpers/constants.js b/src/helpers/constants.js index 261c69f5..560eb41c 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/helpers/error-messages.js b/src/helpers/error-messages.js index 48be037d..663fdd31 100644 --- a/src/helpers/error-messages.js +++ b/src/helpers/error-messages.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/helpers/errors.js b/src/helpers/errors.js index 95aab9a9..0c8f47b2 100644 --- a/src/helpers/errors.js +++ b/src/helpers/errors.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/helpers/system-naming.js b/src/helpers/system-naming.js index f3a7132e..369b033d 100644 --- a/src/helpers/system-naming.js +++ b/src/helpers/system-naming.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/init.js b/src/init.js index dc5b76ff..8d09c028 100644 --- a/src/init.js +++ b/src/init.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/jobs/controller-cleanup-job.js b/src/jobs/controller-cleanup-job.js index 449ac4ef..9c0977fb 100644 --- a/src/jobs/controller-cleanup-job.js +++ b/src/jobs/controller-cleanup-job.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/jobs/controller-heartbeat-job.js b/src/jobs/controller-heartbeat-job.js index 3b960733..17065ed8 100644 --- a/src/jobs/controller-heartbeat-job.js +++ b/src/jobs/controller-heartbeat-job.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/jobs/event-cleanup-job.js b/src/jobs/event-cleanup-job.js index 7ff0b74e..c71b0beb 100644 --- a/src/jobs/event-cleanup-job.js +++ b/src/jobs/event-cleanup-job.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 diff --git a/src/jobs/fog-status-job.js b/src/jobs/fog-status-job.js index 2d1f167c..894c92cc 100644 --- a/src/jobs/fog-status-job.js +++ b/src/jobs/fog-status-job.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/jobs/fog-token-cleanup-job.js b/src/jobs/fog-token-cleanup-job.js index 1cce9459..be90a7a8 100644 --- a/src/jobs/fog-token-cleanup-job.js +++ b/src/jobs/fog-token-cleanup-job.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/jobs/nats-reconcile-worker-job.js b/src/jobs/nats-reconcile-worker-job.js index 02a28275..4e97ef48 100644 --- a/src/jobs/nats-reconcile-worker-job.js +++ b/src/jobs/nats-reconcile-worker-job.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/jobs/stopped-app-status-job.js b/src/jobs/stopped-app-status-job.js index ea07f2bd..29411185 100644 --- a/src/jobs/stopped-app-status-job.js +++ b/src/jobs/stopped-app-status-job.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/keycloak.json b/src/keycloak.json index cfc7f6b3..4e21d03e 100644 --- a/src/keycloak.json +++ b/src/keycloak.json @@ -1,9 +1,9 @@ { - "realm": "datasance", + "realm": "iofog", "realm-public-key": "", "auth-server-url": "", "ssl-required": "", - "resource": "pot-controller", + "resource": "iofog-controller", "bearer-only":true, "verify-token-audience": true, "credentials": { diff --git a/src/lib/rbac/authorizer.js b/src/lib/rbac/authorizer.js index 87faaedb..b491b8bc 100644 --- a/src/lib/rbac/authorizer.js +++ b/src/lib/rbac/authorizer.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/lib/rbac/middleware.js b/src/lib/rbac/middleware.js index 017b176c..8c4e67aa 100644 --- a/src/lib/rbac/middleware.js +++ b/src/lib/rbac/middleware.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/logger/index.js b/src/logger/index.js index 91548799..9da1a718 100644 --- a/src/logger/index.js +++ b/src/logger/index.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/main.js b/src/main.js index bb7ec0cf..d1e7d978 100644 --- a/src/main.js +++ b/src/main.js @@ -2,7 +2,7 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/middlewares/event-audit-middleware.js b/src/middlewares/event-audit-middleware.js index af1a9892..2fc2bb08 100644 --- a/src/middlewares/event-audit-middleware.js +++ b/src/middlewares/event-audit-middleware.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 diff --git a/src/routes/agent.js b/src/routes/agent.js index 986cf3c0..9049fde2 100644 --- a/src/routes/agent.js +++ b/src/routes/agent.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/application.js b/src/routes/application.js index 3f403182..470f79f0 100644 --- a/src/routes/application.js +++ b/src/routes/application.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/applicationTemplate.js b/src/routes/applicationTemplate.js index 1f46be64..a8ab0894 100644 --- a/src/routes/applicationTemplate.js +++ b/src/routes/applicationTemplate.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/capabilities.js b/src/routes/capabilities.js index 9f576ae1..9cd4d463 100644 --- a/src/routes/capabilities.js +++ b/src/routes/capabilities.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/catalog.js b/src/routes/catalog.js index e794da14..b7f1890d 100644 --- a/src/routes/catalog.js +++ b/src/routes/catalog.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/cluster.js b/src/routes/cluster.js index c49595ab..e7c67cd1 100644 --- a/src/routes/cluster.js +++ b/src/routes/cluster.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/config.js b/src/routes/config.js index edae06ba..45910094 100644 --- a/src/routes/config.js +++ b/src/routes/config.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/configMap.js b/src/routes/configMap.js index 3e96e2d3..c9bcddb9 100644 --- a/src/routes/configMap.js +++ b/src/routes/configMap.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/controller.js b/src/routes/controller.js index 36e9fb8f..5e425d65 100644 --- a/src/routes/controller.js +++ b/src/routes/controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/diagnostics.js b/src/routes/diagnostics.js index cf2d4dd8..4783dbac 100644 --- a/src/routes/diagnostics.js +++ b/src/routes/diagnostics.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/edgeResource.js b/src/routes/edgeResource.js index 54208811..693069fd 100644 --- a/src/routes/edgeResource.js +++ b/src/routes/edgeResource.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/event.js b/src/routes/event.js index e9b5e082..a99e4301 100644 --- a/src/routes/event.js +++ b/src/routes/event.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 diff --git a/src/routes/flow.js b/src/routes/flow.js index 3de28156..9e0b6854 100644 --- a/src/routes/flow.js +++ b/src/routes/flow.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/iofog.js b/src/routes/iofog.js index 12801059..09dc6a40 100644 --- a/src/routes/iofog.js +++ b/src/routes/iofog.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/microservices.js b/src/routes/microservices.js index 27a5a8b6..94617c21 100644 --- a/src/routes/microservices.js +++ b/src/routes/microservices.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/nats.js b/src/routes/nats.js index 20aaa59d..c174a21c 100644 --- a/src/routes/nats.js +++ b/src/routes/nats.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/rbac.js b/src/routes/rbac.js index 77d97699..e24b3f7b 100644 --- a/src/routes/rbac.js +++ b/src/routes/rbac.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/registries.js b/src/routes/registries.js index 3648a6ff..01ac036e 100644 --- a/src/routes/registries.js +++ b/src/routes/registries.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/router.js b/src/routes/router.js index dbcfa0d3..e32845ac 100644 --- a/src/routes/router.js +++ b/src/routes/router.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/secret.js b/src/routes/secret.js index cda187aa..6d4d418e 100644 --- a/src/routes/secret.js +++ b/src/routes/secret.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/service.js b/src/routes/service.js index 64163af7..de9d3855 100644 --- a/src/routes/service.js +++ b/src/routes/service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/tunnel.js b/src/routes/tunnel.js index c9f115b5..e0266f1b 100644 --- a/src/routes/tunnel.js +++ b/src/routes/tunnel.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/user.js b/src/routes/user.js index f900af89..5f902fb4 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/routes/volumeMount.js b/src/routes/volumeMount.js index a8af91cb..e7f16db2 100644 --- a/src/routes/volumeMount.js +++ b/src/routes/volumeMount.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/schemas/agent.js b/src/schemas/agent.js index 40bd2046..41e02575 100644 --- a/src/schemas/agent.js +++ b/src/schemas/agent.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/schemas/catalog.js b/src/schemas/catalog.js index 4b61d9a1..a799505e 100644 --- a/src/schemas/catalog.js +++ b/src/schemas/catalog.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/schemas/cluster-controller.js b/src/schemas/cluster-controller.js index e3fb335a..e6549c35 100644 --- a/src/schemas/cluster-controller.js +++ b/src/schemas/cluster-controller.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/schemas/config.js b/src/schemas/config.js index 5c4e390b..29cd4c3c 100644 --- a/src/schemas/config.js +++ b/src/schemas/config.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/schemas/controlPlane.js b/src/schemas/controlPlane.js index 79b659a9..b6f3dc3b 100644 --- a/src/schemas/controlPlane.js +++ b/src/schemas/controlPlane.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/schemas/diagnostics.js b/src/schemas/diagnostics.js index 8947c331..413f0d19 100644 --- a/src/schemas/diagnostics.js +++ b/src/schemas/diagnostics.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/schemas/edgeResource.js b/src/schemas/edgeResource.js index 1bc88779..7f8524d9 100644 --- a/src/schemas/edgeResource.js +++ b/src/schemas/edgeResource.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/schemas/index.js b/src/schemas/index.js index 3ec077aa..10aa0126 100644 --- a/src/schemas/index.js +++ b/src/schemas/index.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/schemas/iofog.js b/src/schemas/iofog.js index aaa05d30..5398b4c9 100644 --- a/src/schemas/iofog.js +++ b/src/schemas/iofog.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/schemas/rbac.js b/src/schemas/rbac.js index e9b77efc..8d9986fc 100644 --- a/src/schemas/rbac.js +++ b/src/schemas/rbac.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/schemas/registry.js b/src/schemas/registry.js index 4e31e986..f2e3cec7 100644 --- a/src/schemas/registry.js +++ b/src/schemas/registry.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/schemas/tunnel.js b/src/schemas/tunnel.js index 1b3b0110..22777877 100644 --- a/src/schemas/tunnel.js +++ b/src/schemas/tunnel.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/schemas/user.js b/src/schemas/user.js index d069afeb..1517aa61 100644 --- a/src/schemas/user.js +++ b/src/schemas/user.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/server.js b/src/server.js index 47650a0a..718f3f83 100755 --- a/src/server.js +++ b/src/server.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -22,7 +22,7 @@ initialize().then(() => { const bodyParser = require('body-parser') const cookieParser = require('cookie-parser') const express = require('express') - const ecnViewer = process.env.ECN_VIEWER_PATH ? require(`${process.env.ECN_VIEWER_PATH}/package/index.js`) : require('@datasance/ecn-viewer') + const ecnViewer = process.env.ECN_VIEWER_PATH ? require(`${process.env.ECN_VIEWER_PATH}/package/index.js`) : require('@eclipse-iofog/ecn-viewer') const fs = require('fs') const helmet = require('helmet') const cors = require('cors') @@ -54,7 +54,7 @@ initialize().then(() => { // express logs // app.use(morgan('combined')); app.use(session({ - secret: 'pot-controller', + secret: 'iofog-controller', resave: false, saveUninitialized: true, store: memoryStore @@ -250,7 +250,7 @@ initialize().then(() => { }) } // Set up controller-config.js for ECN Viewer - const ecnViewerControllerConfigFilePath = path.join(__dirname, '..', 'node_modules', '@datasance', 'ecn-viewer', 'build', 'controller-config.js') + const ecnViewerControllerConfigFilePath = path.join(__dirname, '..', 'node_modules', '@eclipse-iofog', 'ecn-viewer', 'build', 'controller-config.js') const ecnViewerControllerConfig = { port: apiPort, user: {}, diff --git a/src/services/agent-service.js b/src/services/agent-service.js index 485bbcc1..840b58d8 100644 --- a/src/services/agent-service.js +++ b/src/services/agent-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -60,7 +60,7 @@ for (const key of CHANGE_TRACKING_KEYS) { const agentProvision = async function (provisionData, transaction) { await Validator.validate(provisionData, Validator.schemas.agentProvision) - const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'datasance') + const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'iofog') const provision = await FogProvisionKeyManager.findOne({ provisionKey: provisionData.key diff --git a/src/services/application-service.js b/src/services/application-service.js index 62553b73..dca43471 100644 --- a/src/services/application-service.js +++ b/src/services/application-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/application-template-service.js b/src/services/application-template-service.js index df68d89c..bf7ef7d9 100644 --- a/src/services/application-template-service.js +++ b/src/services/application-template-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/catalog-service.js b/src/services/catalog-service.js index a3de0cc3..10076cf4 100644 --- a/src/services/catalog-service.js +++ b/src/services/catalog-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -146,7 +146,7 @@ async function getNatsCatalogItem (transaction) { return CatalogItemManager.findOne({ name: 'NATs', category: 'SYSTEM', - publisher: 'Datasance', + publisher: 'Eclipse ioFog', registry_id: 1 }, transaction) } @@ -155,7 +155,7 @@ async function getRouterCatalogItem (transaction) { return CatalogItemManager.findOne({ name: DBConstants.ROUTER_CATALOG_NAME, category: 'SYSTEM', - publisher: 'Datasance', + publisher: 'Eclipse ioFog', registry_id: 1 }, transaction) } @@ -164,7 +164,7 @@ async function getDebugCatalogItem (transaction) { return CatalogItemManager.findOne({ name: DBConstants.DEBUG_CATALOG_NAME, category: 'SYSTEM', - publisher: 'Datasance', + publisher: 'Eclipse ioFog', registry_id: 1 }, transaction) } @@ -173,7 +173,7 @@ async function getBluetoothCatalogItem (transaction) { return CatalogItemManager.findOne({ name: 'RESTBlue', category: 'SYSTEM', - publisher: 'Datasance', + publisher: 'Eclipse ioFog', registry_id: 1 }, transaction) } @@ -182,7 +182,7 @@ async function getHalCatalogItem (transaction) { return CatalogItemManager.findOne({ name: 'HAL', category: 'SYSTEM', - publisher: 'Datasance', + publisher: 'Eclipse ioFog', registry_id: 1 }, transaction) } diff --git a/src/services/change-tracking-service.js b/src/services/change-tracking-service.js index 221b8f66..1ed1c451 100644 --- a/src/services/change-tracking-service.js +++ b/src/services/change-tracking-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/cluster-controller-service.js b/src/services/cluster-controller-service.js index 85039018..b3c5ce03 100644 --- a/src/services/cluster-controller-service.js +++ b/src/services/cluster-controller-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/config-map-service.js b/src/services/config-map-service.js index 25fb1859..359e688d 100644 --- a/src/services/config-map-service.js +++ b/src/services/config-map-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/config-service.js b/src/services/config-service.js index 63cab6f6..b17d0af1 100644 --- a/src/services/config-service.js +++ b/src/services/config-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/controller-service.js b/src/services/controller-service.js index 79fa63ae..d066f15d 100644 --- a/src/services/controller-service.js +++ b/src/services/controller-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -49,7 +49,7 @@ const statusController = async function (isCLI) { 'uptimeSec': process.uptime(), versions: { controller: packageJson.version, - ecnViewer: packageJson.dependencies['@datasance/ecn-viewer'] + ecnViewer: packageJson.dependencies['@eclipse-iofog/ecn-viewer'] } } } diff --git a/src/services/diagnostic-service.js b/src/services/diagnostic-service.js index 41ace819..02531cc0 100644 --- a/src/services/diagnostic-service.js +++ b/src/services/diagnostic-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/edge-resource-service.js b/src/services/edge-resource-service.js index 10e943ca..ca0e36bc 100644 --- a/src/services/edge-resource-service.js +++ b/src/services/edge-resource-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/event-service.js b/src/services/event-service.js index 78f5ee76..bcb878df 100644 --- a/src/services/event-service.js +++ b/src/services/event-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 diff --git a/src/services/iofog-key-service.js b/src/services/iofog-key-service.js index 134f5556..21ffef87 100644 --- a/src/services/iofog-key-service.js +++ b/src/services/iofog-key-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/iofog-service.js b/src/services/iofog-service.js index 8b4fa151..47af109d 100644 --- a/src/services/iofog-service.js +++ b/src/services/iofog-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -61,7 +61,7 @@ const FogPublicKeyManager = require('../data/managers/iofog-public-key-manager') const SITE_CA_CERT = 'router-site-ca' const DEFAULT_ROUTER_LOCAL_CA = 'default-router-local-ca' -const SERVICE_ANNOTATION_TAG = 'service.datasance.com/tag' +const SERVICE_ANNOTATION_TAG = 'service.iofog.org/tag' async function checkKubernetesEnvironment () { const controlPlane = process.env.CONTROL_PLANE || config.get('app.ControlPlane') diff --git a/src/services/microservice-ports/microservice-port.js b/src/services/microservice-ports/microservice-port.js index e866f191..5ff243d1 100644 --- a/src/services/microservice-ports/microservice-port.js +++ b/src/services/microservice-ports/microservice-port.js @@ -1,6 +1,6 @@ /* only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/microservices-service.js b/src/services/microservices-service.js index e29de2ef..486696fd 100644 --- a/src/services/microservices-service.js +++ b/src/services/microservices-service.js @@ -1,6 +1,6 @@ /* only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/nats-api-service.js b/src/services/nats-api-service.js index db37e1a3..8c092fea 100644 --- a/src/services/nats-api-service.js +++ b/src/services/nats-api-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/nats-auth-service.js b/src/services/nats-auth-service.js index 01a071da..e5386afd 100644 --- a/src/services/nats-auth-service.js +++ b/src/services/nats-auth-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/nats-hub-service.js b/src/services/nats-hub-service.js index 82264584..41e8a55b 100644 --- a/src/services/nats-hub-service.js +++ b/src/services/nats-hub-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/nats-service.js b/src/services/nats-service.js index d1cd72a8..32bef26b 100644 --- a/src/services/nats-service.js +++ b/src/services/nats-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/rbac-service.js b/src/services/rbac-service.js index 1c4891c6..24e2f305 100644 --- a/src/services/rbac-service.js +++ b/src/services/rbac-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/registry-service.js b/src/services/registry-service.js index 7df9a2ab..298e78bf 100644 --- a/src/services/registry-service.js +++ b/src/services/registry-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/router-service.js b/src/services/router-service.js index 25bad1a0..73a15f0a 100644 --- a/src/services/router-service.js +++ b/src/services/router-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -39,7 +39,7 @@ const { getSystemMicroserviceName } = require('../helpers/system-naming') -const SITE_CONFIG_VERSION = 'pot' +const SITE_CONFIG_VERSION = 'iofog' const SITE_CONFIG_NAMESPACE = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace') const SSL_PROFILE_PATH = '/etc/skupper-router-certs' const SYSTEM_DEFAULT_CA_PATH = '/etc/pki/tls/certs/ca-bundle.crt' @@ -378,7 +378,7 @@ async function _createRouterMicroservice (isEdge, uuid, microserviceConfig, tran }, { key: 'SKUPPER_PLATFORM', - value: 'pot' + value: 'iofog' } ] } diff --git a/src/services/secret-service.js b/src/services/secret-service.js index 97e3eecc..85329fab 100644 --- a/src/services/secret-service.js +++ b/src/services/secret-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/services-service.js b/src/services/services-service.js index da4bc45e..e174b05c 100644 --- a/src/services/services-service.js +++ b/src/services/services-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -34,7 +34,7 @@ const { // const { Op } = require('sequelize') const K8S_ROUTER_CONFIG_MAP = 'iofog-router' -const SERVICE_ANNOTATION_TAG = 'service.datasance.com/tag' +const SERVICE_ANNOTATION_TAG = 'service.iofog.org/tag' // Map service tags to string array // Return plain JS object @@ -848,10 +848,10 @@ async function _deleteTcpListener (serviceName, transaction) { // Common labels for Kubernetes services created by the controller function _getK8sServiceLabels () { return { - 'app.kubernetes.io/name': 'pot', + 'app.kubernetes.io/name': 'iofog', 'app.kubernetes.io/component': 'controller', 'app.kubernetes.io/managed-by': 'controller', - 'datasance.com/component': 'router', + 'iofog.org/component': 'router', 'app.kubernetes.io/instance': process.env.CONTROLLER_NAME || config.get('app.name') } } @@ -874,10 +874,10 @@ async function _createK8sService (serviceConfig, transaction) { spec: { type: serviceConfig.k8sType, selector: { - 'datasance.com/component': 'router' + 'iofog.org/component': 'router' }, ports: [{ - name: 'pot-service', + name: 'iofog-service', targetPort: parseInt(serviceConfig.bridgePort), port: parseInt(serviceConfig.servicePort), protocol: 'TCP' @@ -923,10 +923,10 @@ async function _updateK8sService (serviceConfig, transaction) { spec: { type: serviceConfig.k8sType, selector: { - 'datasance.com/component': 'router' + 'iofog.org/component': 'router' }, ports: [{ - name: 'pot-service', + name: 'iofog-service', port: parseInt(serviceConfig.servicePort), targetPort: parseInt(serviceConfig.bridgePort), protocol: 'TCP' diff --git a/src/services/tunnel-service.js b/src/services/tunnel-service.js index 7c013b26..d9434481 100644 --- a/src/services/tunnel-service.js +++ b/src/services/tunnel-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/services/user-service.js b/src/services/user-service.js index 639b1fef..1ce0136f 100644 --- a/src/services/user-service.js +++ b/src/services/user-service.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/utils/ssl-utils.js b/src/utils/ssl-utils.js index 6a0a1200..c20b75de 100644 --- a/src/utils/ssl-utils.js +++ b/src/utils/ssl-utils.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/vault/aws-secrets-manager-provider.js b/src/vault/aws-secrets-manager-provider.js index 5faa3a71..f001321b 100644 --- a/src/vault/aws-secrets-manager-provider.js +++ b/src/vault/aws-secrets-manager-provider.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -77,7 +77,7 @@ class AWSSecretsManagerProvider extends BaseVaultProvider { const command = new this.CreateSecretCommand({ Name: secretName, SecretString: JSON.stringify(data), - Description: `Datasance PoT controller secret: ${path}` + Description: `Eclipse ioFog controller secret: ${path}` }) try { @@ -184,7 +184,7 @@ class AWSSecretsManagerProvider extends BaseVaultProvider { if (this.config && this.config.basePath && typeof this.config.basePath === 'string') { return this.config.basePath } - return 'pot-controller/secrets' + return 'iofog-controller/secrets' } } diff --git a/src/vault/azure-key-vault-provider.js b/src/vault/azure-key-vault-provider.js index 7158ba9a..17f067a0 100644 --- a/src/vault/azure-key-vault-provider.js +++ b/src/vault/azure-key-vault-provider.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -159,7 +159,7 @@ class AzureKeyVaultProvider extends BaseVaultProvider { if (this.config && this.config.basePath && typeof this.config.basePath === 'string') { return this.config.basePath } - return 'pot-controller/secrets' + return 'iofog-controller/secrets' } } diff --git a/src/vault/base-vault-provider.js b/src/vault/base-vault-provider.js index f509c2c1..9e32cc28 100644 --- a/src/vault/base-vault-provider.js +++ b/src/vault/base-vault-provider.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/src/vault/google-secret-manager-provider.js b/src/vault/google-secret-manager-provider.js index e26dc30b..543450fd 100644 --- a/src/vault/google-secret-manager-provider.js +++ b/src/vault/google-secret-manager-provider.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -215,7 +215,7 @@ class GoogleSecretManagerProvider extends BaseVaultProvider { if (this.config && this.config.basePath && typeof this.config.basePath === 'string') { return this.config.basePath } - return 'pot-controller/secrets' + return 'iofog-controller/secrets' } } diff --git a/src/vault/hashicorp-vault-provider.js b/src/vault/hashicorp-vault-provider.js index 1724e018..5152cd30 100644 --- a/src/vault/hashicorp-vault-provider.js +++ b/src/vault/hashicorp-vault-provider.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -224,7 +224,7 @@ class HashiCorpVaultProvider extends BaseVaultProvider { if (this.config && this.config.basePath && typeof this.config.basePath === 'string') { return this.config.basePath } - return 'pot-controller/secrets' + return 'iofog-controller/secrets' } } diff --git a/src/vault/vault-manager.js b/src/vault/vault-manager.js index bc2c2f0f..85694b9b 100644 --- a/src/vault/vault-manager.js +++ b/src/vault/vault-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -30,8 +30,8 @@ class VaultManager { */ _getBasePath () { // Get basePath from env var or config, with default - const basePath = process.env.VAULT_BASE_PATH || config.get('vault.basePath', 'pot/$namespace/secrets') - const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'datasance') + const basePath = process.env.VAULT_BASE_PATH || config.get('vault.basePath', 'iofog/$namespace/secrets') + const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'iofog') // Replace $namespace variable return basePath.replace(/\$namespace/g, namespace) diff --git a/src/websocket/log-session-manager.js b/src/websocket/log-session-manager.js index 9998b772..d9f1cc29 100644 --- a/src/websocket/log-session-manager.js +++ b/src/websocket/log-session-manager.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/swagger.js b/swagger.js index 4240fdbb..55dc32f6 100644 --- a/swagger.js +++ b/swagger.js @@ -8,9 +8,9 @@ const swaggerOptions = { swaggerDefinition: { openapi: '3.0.0', info: { - title: 'Datasance PoT Controller REST API Documentation', - version: '3.5.0', - description: 'Datasance PoT Controller REST API Documentation' + title: 'Eclipse ioFog Controller REST API Documentation', + version: '3.7.0', + description: 'Eclipse ioFog Controller REST API Documentation' }, servers: [ { From 9d7c01d7a70a73f9dc09fcca87d581ab72942d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 03:16:51 +0300 Subject: [PATCH 02/75] Add v3.8.0 greenfield database migrations for all dialects Consolidated schema replaces incremental v1.1.0: Architectures table, Applications (was Flows), dropped legacy agent message columns and deprecated edge-resource tables. --- src/data/migrations/README.md | 20 + .../mysql/db_migration_mysql_v3.8.0.sql | 976 ++++++++++++++++++ .../postgres/db_migration_pg_v3.8.0.sql | 966 +++++++++++++++++ .../sqlite/db_migration_sqlite_v3.8.0.sql | 965 +++++++++++++++++ 4 files changed, 2927 insertions(+) create mode 100644 src/data/migrations/README.md create mode 100644 src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql create mode 100644 src/data/migrations/postgres/db_migration_pg_v3.8.0.sql create mode 100644 src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql diff --git a/src/data/migrations/README.md b/src/data/migrations/README.md new file mode 100644 index 00000000..d310cabc --- /dev/null +++ b/src/data/migrations/README.md @@ -0,0 +1,20 @@ +# Database migrations + +## Fresh install (Controller v3.8+) + +New installations use **`db_migration_*_v3.8.0.sql`** and **`db_seeder_*_v3.8.0.sql`** +(sqlite, mysql, postgres). The migration runner in `src/data/providers/database-provider.js` +records schema version **`3.8.0`**. + +Greenfield only: wipe the data directory or database before upgrading from pre-3.8 builds. +There is no v3.7→v3.8 incremental migrator. + +## Pre-3.8 historical files + +| File pattern | Purpose | +|--------------|---------| +| `db_migration_*_v1.1.0.sql` | Legacy incremental schema through Controller ≤3.7 | +| `db_seeder_*_v1.0.2.sql` | Legacy seed data (`FogTypes`, `registry.hub.docker.com`) | + +These files remain in the tree for reference only. **Do not** point the migration runner at them +for new installs. diff --git a/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql b/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql new file mode 100644 index 00000000..adf95e7a --- /dev/null +++ b/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql @@ -0,0 +1,976 @@ +START TRANSACTION; + +CREATE TABLE IF NOT EXISTS Applications ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) UNIQUE, + description VARCHAR(255) DEFAULT '', + is_activated BOOLEAN DEFAULT false, + is_system BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + nats_access BOOLEAN DEFAULT false, + nats_rule_id INTEGER +); + +CREATE TABLE IF NOT EXISTS Registries ( + id INT AUTO_INCREMENT PRIMARY KEY, + url VARCHAR(255), + is_public BOOLEAN, + user_name TEXT, + password TEXT, + user_email TEXT +); + +CREATE TABLE IF NOT EXISTS CatalogItems ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) UNIQUE, + description VARCHAR(255), + category TEXT, + config_example VARCHAR(255) DEFAULT '{}', + publisher TEXT, + disk_required BIGINT DEFAULT 0, + ram_required BIGINT DEFAULT 0, + picture VARCHAR(255) DEFAULT 'images/shared/default.png', + is_public BOOLEAN DEFAULT false, + registry_id INT, + FOREIGN KEY (registry_id) REFERENCES Registries (id) ON DELETE SET NULL +); + +CREATE INDEX idx_catalog_item_registry_id ON CatalogItems (registry_id); + +CREATE TABLE IF NOT EXISTS Architectures ( + id INT PRIMARY KEY, + name TEXT, + image TEXT, + description TEXT, + network_catalog_item_id INT, + hal_catalog_item_id INT, + bluetooth_catalog_item_id INT, + FOREIGN KEY (network_catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (hal_catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (bluetooth_catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE +); + +CREATE INDEX idx_architecture_network_catalog_item_id ON Architectures (network_catalog_item_id); +CREATE INDEX idx_architecture_hal_catalog_item_id ON Architectures (hal_catalog_item_id); +CREATE INDEX idx_architecture_bluetooth_catalog_item_id ON Architectures (bluetooth_catalog_item_id); + +CREATE TABLE IF NOT EXISTS Fogs ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + name VARCHAR(255) DEFAULT 'Unnamed ioFog 1', + location TEXT, + gps_mode TEXT, + latitude FLOAT, + longitude FLOAT, + description TEXT, + last_active BIGINT, + daemon_status VARCHAR(36) DEFAULT 'NOT_PROVISIONED', + daemon_operating_duration BIGINT DEFAULT 0, + daemon_last_start BIGINT, + memory_usage FLOAT DEFAULT 0.000, + disk_usage FLOAT DEFAULT 0.000, + cpu_usage FLOAT DEFAULT 0.00, + memory_violation TEXT, + disk_violation TEXT, + cpu_violation TEXT, + system_available_disk BIGINT, + system_available_memory BIGINT, + system_total_cpu FLOAT, + security_status VARCHAR(36) DEFAULT 'OK', + security_violation_info VARCHAR(36) DEFAULT 'No violation', + catalog_item_status TEXT, + repository_count BIGINT DEFAULT 0, + repository_status TEXT, + system_time BIGINT, + last_status_time BIGINT, + ip_address VARCHAR(36) DEFAULT '0.0.0.0', + ip_address_external VARCHAR(36) DEFAULT '0.0.0.0', + host VARCHAR(36), + catalog_item_message_counts TEXT, + last_command_time BIGINT, + network_interface VARCHAR(36) DEFAULT 'dynamic', + docker_url VARCHAR(255) DEFAULT 'unix:///var/run/docker.sock', + disk_limit FLOAT DEFAULT 50, + disk_directory VARCHAR(255) DEFAULT '/var/lib/iofog-agent/', + memory_limit FLOAT DEFAULT 4096, + cpu_limit FLOAT DEFAULT 80, + log_limit FLOAT DEFAULT 10, + log_directory VARCHAR(255) DEFAULT '/var/log/iofog/', + bluetooth BOOLEAN DEFAULT FALSE, + hal BOOLEAN DEFAULT FALSE, + log_file_count BIGINT DEFAULT 10, + `version` TEXT, + is_ready_to_upgrade BOOLEAN DEFAULT TRUE, + is_ready_to_rollback BOOLEAN DEFAULT FALSE, + status_frequency INT DEFAULT 10, + change_frequency INT DEFAULT 20, + device_scan_frequency INT DEFAULT 20, + tunnel VARCHAR(255) DEFAULT '', + isolated_docker_container BOOLEAN DEFAULT FALSE, + docker_pruning_freq INT DEFAULT 0, + available_disk_threshold FLOAT DEFAULT 20, + log_level VARCHAR(10) DEFAULT 'INFO', + is_system BOOLEAN DEFAULT FALSE, + router_id INT DEFAULT 0, + time_zone VARCHAR(36) DEFAULT 'Etc/UTC', + created_at DATETIME, + updated_at DATETIME, + arch_id INT DEFAULT 0, + container_engine VARCHAR(36), + deployment_type VARCHAR(36), + active_volume_mounts BIGINT DEFAULT 0, + volume_mount_last_update BIGINT DEFAULT 0, + warning_message TEXT DEFAULT 'HEALTHY', + gps_device VARCHAR(36), + gps_scan_frequency INT DEFAULT 60, + edge_guard_frequency INT DEFAULT 0, + gps_status VARCHAR(32), + nats_id INT, + available_runtimes TEXT, + FOREIGN KEY (arch_id) REFERENCES Architectures (id) +); + +CREATE INDEX idx_fog_arch_id ON Fogs (arch_id); + +CREATE TABLE IF NOT EXISTS ChangeTrackings ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + microservice_config BOOLEAN DEFAULT false, + reboot BOOLEAN DEFAULT false, + deletenode BOOLEAN DEFAULT false, + version BOOLEAN DEFAULT false, + microservice_list BOOLEAN DEFAULT false, + config BOOLEAN DEFAULT false, + registries BOOLEAN DEFAULT false, + tunnel BOOLEAN DEFAULT false, + diagnostics BOOLEAN DEFAULT false, + router_changed BOOLEAN DEFAULT false, + image_snapshot BOOLEAN DEFAULT false, + prune BOOLEAN DEFAULT false, + last_updated VARCHAR(255) DEFAULT false, + iofog_uuid VARCHAR(36), + volume_mounts BOOLEAN DEFAULT false, + exec_sessions BOOLEAN DEFAULT false, + microservice_logs BOOLEAN DEFAULT false, + fog_logs BOOLEAN DEFAULT false, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_change_tracking_iofog_uuid ON ChangeTrackings (iofog_uuid); + +CREATE TABLE IF NOT EXISTS FogProvisionKeys ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + provisioning_string VARCHAR(100), + expiration_time BIGINT, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_provision_keys_iofogUuid ON FogProvisionKeys (iofog_uuid); + +CREATE TABLE IF NOT EXISTS FogVersionCommands ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + version_command VARCHAR(100), + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_version_commands_iofogUuid ON FogVersionCommands (iofog_uuid); + +CREATE TABLE IF NOT EXISTS HWInfos ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + info TEXT, + created_at DATETIME, + updated_at DATETIME, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_hw_infos_iofogUuid ON HWInfos (iofog_uuid); + +CREATE TABLE IF NOT EXISTS USBInfos ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + info TEXT, + created_at DATETIME, + updated_at DATETIME, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_usb_infos_iofogUuid ON USBInfos (iofog_uuid); + +CREATE TABLE IF NOT EXISTS Tunnels ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + username TEXT, + password TEXT, + host TEXT, + remote_port INT, + local_port INT DEFAULT 22, + rsa_key TEXT, + closed BOOLEAN DEFAULT false, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_tunnels_iofogUuid ON Tunnels (iofog_uuid); + +CREATE TABLE IF NOT EXISTS Microservices ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + config TEXT, + name VARCHAR(255) DEFAULT 'New Microservice', + config_last_updated BIGINT, + rebuild BOOLEAN DEFAULT false, + root_host_access BOOLEAN DEFAULT false, + log_size BIGINT DEFAULT 0, + image_snapshot VARCHAR(255) DEFAULT '', + `delete` BOOLEAN DEFAULT false, + delete_with_cleanup BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + catalog_item_id INT, + registry_id INT DEFAULT 1, + iofog_uuid VARCHAR(36), + application_id INT, + annotations TEXT, + pid_mode VARCHAR(36), + ipc_mode VARCHAR(36), + exec_enabled BOOLEAN DEFAULT false, + schedule INT DEFAULT 50, + cpu_set_cpus TEXT, + memory_limit FLOAT, + is_activated BOOLEAN DEFAULT true, + host_network_mode BOOLEAN DEFAULT false, + is_privileged BOOLEAN DEFAULT false, + nats_rule_id INT, + nats_access BOOLEAN DEFAULT false, + nats_account_id INT, + nats_user_id INT, + nats_creds_secret_name TEXT, + is_controller BOOLEAN DEFAULT false, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (registry_id) REFERENCES Registries (id) ON DELETE SET NULL, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES Applications (id) ON DELETE CASCADE +); + +CREATE INDEX idx_microservices_catalogItemId ON Microservices (catalog_item_id); +CREATE INDEX idx_microservices_registryId ON Microservices (registry_id); +CREATE INDEX idx_microservices_iofogUuid ON Microservices (iofog_uuid); +CREATE INDEX idx_microservices_applicationId ON Microservices (application_id); + +CREATE TABLE IF NOT EXISTS MicroserviceArgs ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + cmd TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_args_microserviceUuid ON MicroserviceArgs (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceEnvs ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + `key` TEXT, + `value` TEXT, + microservice_uuid VARCHAR(36), + value_from_secret TEXT, + value_from_config_map TEXT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_envs_microserviceUuid ON MicroserviceEnvs (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceExtraHost ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + template_type TEXT, + name TEXT, + public_port INT, + template TEXT, + `value` TEXT, + microservice_uuid VARCHAR(36), + target_microservice_uuid VARCHAR(36), + target_fog_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (target_microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (target_fog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_extra_host_microserviceUuid ON MicroserviceExtraHost (microservice_uuid); +CREATE INDEX idx_microservice_extra_host_targetMicroserviceUuid ON MicroserviceExtraHost (target_microservice_uuid); +CREATE INDEX idx_microservice_extra_host_targetFogUuid ON MicroserviceExtraHost (target_fog_uuid); + +CREATE TABLE IF NOT EXISTS MicroservicePorts ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + port_internal INT, + port_external INT, + is_udp BOOLEAN, + is_public BOOLEAN, + is_proxy BOOLEAN, + created_at DATETIME, + updated_at DATETIME, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_port_microserviceUuid ON MicroservicePorts (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceStatuses ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + status VARCHAR(255) DEFAULT 'QUEUED', + operating_duration BIGINT DEFAULT 0, + start_time BIGINT DEFAULT 0, + cpu_usage FLOAT DEFAULT 0.000, + memory_usage BIGINT DEFAULT 0, + container_id VARCHAR(255) DEFAULT '', + percentage FLOAT DEFAULT 0.00, + error_message TEXT, + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + ip_address TEXT, + exec_session_ids TEXT, + health_status TEXT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_status_microserviceUuid ON MicroserviceStatuses (microservice_uuid); + +CREATE TABLE IF NOT EXISTS VolumeMappings ( + uuid INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + host_destination TEXT, + container_destination TEXT, + access_mode TEXT, + type TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_volume_mappings_microserviceUuid ON VolumeMappings (microservice_uuid); + +CREATE TABLE IF NOT EXISTS CatalogItemImages ( + id INT AUTO_INCREMENT PRIMARY KEY, + container_image TEXT, + catalog_item_id INT, + microservice_uuid VARCHAR(36), + arch_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (arch_id) REFERENCES Architectures (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_image_catalog_item_id ON CatalogItemImages (catalog_item_id); +CREATE INDEX idx_catalog_item_image_microservice_uuid ON CatalogItemImages (microservice_uuid); +CREATE INDEX idx_catalog_item_image_arch_id ON CatalogItemImages (arch_id); + +CREATE TABLE IF NOT EXISTS CatalogItemInputTypes ( + id INT AUTO_INCREMENT PRIMARY KEY, + info_type TEXT, + info_format TEXT, + catalog_item_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_input_type_catalog_item_id ON CatalogItemInputTypes (catalog_item_id); + +CREATE TABLE IF NOT EXISTS CatalogItemOutputTypes ( + id INT AUTO_INCREMENT PRIMARY KEY, + info_type TEXT, + info_format TEXT, + catalog_item_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_output_type_catalog_item_id ON CatalogItemOutputTypes (catalog_item_id); + +CREATE TABLE IF NOT EXISTS Routers ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + is_edge BOOLEAN DEFAULT true, + messaging_port INT DEFAULT 5671, + edge_router_port INT, + inter_router_port INT, + host TEXT, + is_default BOOLEAN DEFAULT false, + iofog_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_router_iofogUuid ON Routers (iofog_uuid); + +CREATE TABLE RouterConnections ( + id INT AUTO_INCREMENT PRIMARY KEY, + source_router INT, + dest_router INT, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (source_router) REFERENCES Routers(id) ON DELETE CASCADE, + FOREIGN KEY (dest_router) REFERENCES Routers(id) ON DELETE CASCADE +); + +CREATE INDEX idx_routerconnections_sourceRouter ON RouterConnections (source_router); +CREATE INDEX idx_routerconnections_destRouter ON RouterConnections (dest_router); + +CREATE TABLE IF NOT EXISTS Config ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + `key` VARCHAR(255) NOT NULL UNIQUE, + value VARCHAR(255) NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_config_key ON Config (`key`); + +CREATE TABLE IF NOT EXISTS Tags ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + value VARCHAR(255) UNIQUE NOT NULL +); + +CREATE TABLE IF NOT EXISTS IofogTags ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + fog_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (fog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE INDEX idx_iofogtags_fog_uuid ON IofogTags (fog_uuid); +CREATE INDEX idx_iofogtags_tag_id ON IofogTags (tag_id); + +CREATE TABLE IF NOT EXISTS ApplicationTemplates ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL DEFAULT 'new-application', + description VARCHAR(255) DEFAULT '', + schema_version VARCHAR(255) DEFAULT '', + application_json LONGTEXT, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS ApplicationTemplateVariables ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + application_template_id INT NOT NULL, + `key` TEXT, + description VARCHAR(255) DEFAULT '', + default_value VARCHAR(255), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (application_template_id) REFERENCES ApplicationTemplates (id) ON DELETE CASCADE +); + +CREATE INDEX idx_applicationtemplatevariables_application_template_id ON ApplicationTemplateVariables (application_template_id); + +CREATE TABLE IF NOT EXISTS MicroserviceCdiDevices ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + cdi_devices TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_cdiDevices_microserviceUuid ON MicroserviceCdiDevices (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroservicePubTags ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + microservice_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS MicroserviceSubTags ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + microservice_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE INDEX idx_microservicepubtags_microservice_uuid ON MicroservicePubTags (microservice_uuid); +CREATE INDEX idx_microservicesubtags_microservice_uuid ON MicroserviceSubTags (microservice_uuid); +CREATE INDEX idx_microservicepubtags_tag_id ON MicroservicePubTags (tag_id); +CREATE INDEX idx_microservicesubtags_tag_id ON MicroserviceSubTags (tag_id); + +CREATE TABLE IF NOT EXISTS MicroserviceCapAdd ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + cap_add TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_capAdd_microserviceUuid ON MicroserviceCapAdd (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceCapDrop ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + cap_drop TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_capDrop_microserviceUuid ON MicroserviceCapDrop (microservice_uuid); + +CREATE TABLE IF NOT EXISTS FogPublicKeys ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + public_key TEXT, + iofog_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_public_keys_iofogUuid ON FogPublicKeys (iofog_uuid); + +CREATE TABLE IF NOT EXISTS FogUsedTokens ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + jti VARCHAR(255) NOT NULL, + iofog_uuid VARCHAR(36), + expiry_time BIGINT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_used_tokens_iofogUuid ON FogUsedTokens (iofog_uuid); + +CREATE TABLE IF NOT EXISTS Secrets ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + type VARCHAR(50) NOT NULL CHECK (type IN ('Opaque', 'tls')), + data TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_secrets_name ON Secrets (name); + +CREATE TABLE IF NOT EXISTS Certificates ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + name TEXT NOT NULL, + subject TEXT NOT NULL, + is_ca BOOLEAN DEFAULT false, + signed_by_id INT, + hosts TEXT, + valid_from DATETIME NOT NULL, + valid_to DATETIME NOT NULL, + serial_number TEXT NOT NULL, + secret_id INT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (signed_by_id) REFERENCES Certificates (id) ON DELETE SET NULL, + FOREIGN KEY (secret_id) REFERENCES Secrets (id) ON DELETE CASCADE +); + +CREATE UNIQUE INDEX idx_certificates_name_unique ON Certificates (name(255)); +CREATE INDEX idx_certificates_valid_to ON Certificates (valid_to); +CREATE INDEX idx_certificates_is_ca ON Certificates (is_ca); +CREATE INDEX idx_certificates_signed_by_id ON Certificates (signed_by_id); +CREATE INDEX idx_certificates_secret_id ON Certificates (secret_id); + +CREATE TABLE IF NOT EXISTS Services ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + type VARCHAR(50) NOT NULL, + resource TEXT NOT NULL, + target_port INT NOT NULL, + service_port INT, + k8s_type TEXT, + bridge_port INT, + default_bridge TEXT, + service_endpoint TEXT, + created_at DATETIME, + updated_at DATETIME, + provisioning_status VARCHAR(36) DEFAULT 'pending', + provisioning_error TEXT +); + +CREATE INDEX idx_services_name ON Services (name); +CREATE INDEX idx_services_id ON Services (id); + +CREATE TABLE IF NOT EXISTS ServiceTags ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + service_id INT NOT NULL, + tag_id INT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (service_id) REFERENCES Services (id) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE INDEX idx_service_tags_service_id ON ServiceTags (service_id); +CREATE INDEX idx_service_tags_tag_id ON ServiceTags (tag_id); + +CREATE TABLE IF NOT EXISTS ConfigMaps ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + immutable BOOLEAN DEFAULT false, + data TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + use_vault BOOLEAN DEFAULT true +); + +CREATE INDEX idx_config_maps_name ON ConfigMaps (name); + +CREATE TABLE IF NOT EXISTS VolumeMounts ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + name VARCHAR(255) NOT NULL, + config_map_name VARCHAR(255), + secret_name VARCHAR(255), + version INT DEFAULT 1, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (config_map_name) REFERENCES ConfigMaps (name) ON DELETE CASCADE, + FOREIGN KEY (secret_name) REFERENCES Secrets (name) ON DELETE CASCADE +); + +CREATE INDEX idx_volume_mounts_uuid ON VolumeMounts (uuid); +CREATE INDEX idx_volume_mounts_config_map_name ON VolumeMounts (config_map_name); +CREATE INDEX idx_volume_mounts_secret_name ON VolumeMounts (secret_name); + +CREATE TABLE IF NOT EXISTS FogVolumeMounts ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + fog_uuid VARCHAR(36), + volume_mount_uuid VARCHAR(36), + FOREIGN KEY (fog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE, + FOREIGN KEY (volume_mount_uuid) REFERENCES VolumeMounts (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_volume_mounts_fog_uuid ON FogVolumeMounts (fog_uuid); +CREATE INDEX idx_fog_volume_mounts_volume_mount_uuid ON FogVolumeMounts (volume_mount_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceExecStatuses ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + status VARCHAR(255) DEFAULT 'INACTIVE', + exec_session_id VARCHAR(255), + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_exec_status_microservice_uuid ON MicroserviceExecStatuses (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceHealthChecks ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + test TEXT, + interval BIGINT, + timeout BIGINT, + start_period BIGINT, + start_interval BIGINT, + retries INT, + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_health_check_microservice_uuid ON MicroserviceHealthChecks (microservice_uuid); + +CREATE TABLE IF NOT EXISTS Events ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + timestamp BIGINT NOT NULL, + event_type VARCHAR(20) NOT NULL, + endpoint_type VARCHAR(10) NOT NULL, + actor_id VARCHAR(255), + method VARCHAR(10), + resource_type VARCHAR(50), + resource_id VARCHAR(255), + endpoint_path TEXT NOT NULL, + ip_address VARCHAR(45), + status VARCHAR(20) NOT NULL, + status_code INT, + status_message TEXT, + request_id VARCHAR(255), + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_events_timestamp ON Events (timestamp); +CREATE INDEX idx_events_endpoint_type ON Events (endpoint_type); +CREATE INDEX idx_events_actor_id ON Events (actor_id); +CREATE INDEX idx_events_resource_type ON Events (resource_type); +CREATE INDEX idx_events_status ON Events (status); +CREATE INDEX idx_events_method ON Events (method); +CREATE INDEX idx_events_event_type ON Events (event_type); +CREATE INDEX idx_events_created_at ON Events (created_at); + +CREATE TABLE IF NOT EXISTS MicroserviceLogStatuses ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + microservice_uuid VARCHAR(36), + log_session_id TEXT, + session_id VARCHAR(255) UNIQUE NOT NULL, + status TEXT, + tail_config TEXT, + agent_connected BOOLEAN DEFAULT false, + user_connected BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_log_status_microservice_uuid ON MicroserviceLogStatuses (microservice_uuid); +CREATE INDEX idx_microservice_log_status_session_id ON MicroserviceLogStatuses (session_id); + +CREATE TABLE IF NOT EXISTS FogLogStatuses ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + iofog_uuid VARCHAR(36), + log_session_id TEXT, + session_id VARCHAR(255) UNIQUE NOT NULL, + status TEXT, + tail_config TEXT, + agent_connected BOOLEAN DEFAULT false, + user_connected BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_log_status_iofog_uuid ON FogLogStatuses (iofog_uuid); +CREATE INDEX idx_fog_log_status_session_id ON FogLogStatuses (session_id); + +CREATE TABLE IF NOT EXISTS RbacRoles ( + id INT AUTO_INCREMENT PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + kind TEXT DEFAULT 'Role', + created_at DATETIME, + updated_at DATETIME, + UNIQUE KEY unique_name (name(255)) +); + +CREATE TABLE IF NOT EXISTS RbacRoleRules ( + id INT AUTO_INCREMENT PRIMARY KEY, + role_id INT NOT NULL, + api_groups TEXT NOT NULL, + resources TEXT NOT NULL, + verbs TEXT NOT NULL, + resource_names TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (role_id) REFERENCES RbacRoles (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS RbacRoleBindings ( + id INT AUTO_INCREMENT PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + kind TEXT DEFAULT 'RoleBinding', + role_ref TEXT NOT NULL, + subjects TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + UNIQUE KEY unique_name (name(255)), + role_id INTEGER +); + +CREATE TABLE IF NOT EXISTS RbacServiceAccounts ( + id INT AUTO_INCREMENT PRIMARY KEY, + name TEXT NOT NULL, + role_ref TEXT, + role_id INT, + microservice_uuid VARCHAR(36), + application_id INT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (role_id) REFERENCES RbacRoles (id), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES Applications (id) ON DELETE SET NULL +); + +CREATE INDEX idx_rbac_role_rules_role_id ON RbacRoleRules (role_id); +CREATE INDEX idx_rbac_roles_name ON RbacRoles (name(255)); +CREATE INDEX idx_rbac_role_bindings_name ON RbacRoleBindings (name(255)); +CREATE INDEX idx_rbac_service_accounts_name ON RbacServiceAccounts (name(255)); + +CREATE TABLE IF NOT EXISTS RbacCacheVersion ( + id INT PRIMARY KEY DEFAULT 1, + version BIGINT NOT NULL DEFAULT 1, + created_at DATETIME, + updated_at DATETIME, + CONSTRAINT single_row CHECK (id = 1) +); + +CREATE INDEX idx_rbac_role_bindings_role_id ON RbacRoleBindings (role_id); + +CREATE INDEX idx_rbac_service_accounts_role_id ON RbacServiceAccounts (role_id); +CREATE UNIQUE INDEX idx_rbac_service_accounts_microservice_uuid_unique ON RbacServiceAccounts (microservice_uuid); +CREATE UNIQUE INDEX idx_rbac_service_accounts_application_id_name_unique ON RbacServiceAccounts (application_id, name); + +CREATE TABLE IF NOT EXISTS ClusterControllers ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + host VARCHAR(255), + process_id INT, + last_heartbeat DATETIME, + is_active BOOLEAN DEFAULT true, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_cluster_controllers_uuid ON ClusterControllers (uuid); +CREATE INDEX idx_cluster_controllers_host ON ClusterControllers (host); +CREATE INDEX idx_cluster_controllers_active ON ClusterControllers (is_active, last_heartbeat); + +CREATE TABLE IF NOT EXISTS NatsOperators ( + id INT AUTO_INCREMENT PRIMARY KEY, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + seed_secret_name TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + UNIQUE KEY nats_operators_name (name(255)) +); + +CREATE TABLE IF NOT EXISTS NatsAccounts ( + id INT AUTO_INCREMENT PRIMARY KEY, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + seed_secret_name TEXT NOT NULL, + is_system BOOLEAN DEFAULT false, + is_leaf_system BOOLEAN DEFAULT false, + operator_id INT NOT NULL, + application_id INT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (operator_id) REFERENCES NatsOperators (id) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES Applications (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS NatsUsers ( + id INT AUTO_INCREMENT PRIMARY KEY, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + creds_secret_name TEXT NOT NULL, + is_bearer BOOLEAN DEFAULT false, + account_id INT NOT NULL, + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + nats_user_rule_id INTEGER, + FOREIGN KEY (account_id) REFERENCES NatsAccounts (id) ON DELETE CASCADE, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS NatsInstances ( + id INT AUTO_INCREMENT PRIMARY KEY, + iofog_uuid VARCHAR(36), + is_leaf BOOLEAN DEFAULT true, + is_hub BOOLEAN DEFAULT false, + host TEXT, + server_port INT, + leaf_port INT, + cluster_port INT, + mqtt_port INT, + http_port INT, + configmap_name TEXT, + jwt_dir_mount_name TEXT, + cert_secret_name TEXT, + js_storage_size TEXT, + js_memory_store_size TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS NatsConnections ( + id INT AUTO_INCREMENT PRIMARY KEY, + source_nats INT NOT NULL, + dest_nats INT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (source_nats) REFERENCES NatsInstances (id) ON DELETE CASCADE, + FOREIGN KEY (dest_nats) REFERENCES NatsInstances (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS NatsReconcileTasks ( + id INT AUTO_INCREMENT PRIMARY KEY, + reason VARCHAR(64) NOT NULL, + application_id INT, + account_rule_id INT, + user_rule_id INT, + fog_uuids TEXT, + status VARCHAR(32) NOT NULL DEFAULT 'pending', + leader_uuid VARCHAR(36), + claimed_at DATETIME, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS NatsAccountRules ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) UNIQUE NOT NULL, + description TEXT, + info_url TEXT, + max_connections INT, + max_leaf_node_connections INT, + max_data BIGINT, + max_exports INT, + max_imports INT, + max_msg_payload INT, + max_subscriptions INT, + exports_allow_wildcards BOOLEAN DEFAULT true, + disallow_bearer BOOLEAN, + response_permissions TEXT, + resp_max INT, + resp_ttl BIGINT, + imports TEXT, + exports TEXT, + mem_storage BIGINT, + disk_storage BIGINT, + streams INT, + consumer INT, + max_ack_pending INT, + mem_max_stream_bytes BIGINT, + disk_max_stream_bytes BIGINT, + max_bytes_required BOOLEAN, + tiered_limits TEXT, + pub_allow TEXT, + pub_deny TEXT, + sub_allow TEXT, + sub_deny TEXT, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS NatsUserRules ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) UNIQUE NOT NULL, + description TEXT, + max_subscriptions INT, + max_payload INT, + max_data BIGINT, + bearer_token BOOLEAN DEFAULT false, + proxy_required BOOLEAN, + allowed_connection_types TEXT, + src TEXT, + times TEXT, + times_location TEXT, + resp_max INT, + resp_ttl BIGINT, + pub_allow TEXT, + pub_deny TEXT, + sub_allow TEXT, + sub_deny TEXT, + tags TEXT, + created_at DATETIME, + updated_at DATETIME +); + +CREATE UNIQUE INDEX idx_nats_accounts_application_id_unique ON NatsAccounts (application_id); +CREATE INDEX idx_nats_accounts_application_id ON NatsAccounts (application_id); +CREATE UNIQUE INDEX idx_nats_users_account_id_name ON NatsUsers (account_id, name(255)); +CREATE INDEX idx_nats_users_account_id ON NatsUsers (account_id); +CREATE INDEX idx_nats_users_microservice_uuid ON NatsUsers (microservice_uuid); +CREATE INDEX idx_nats_users_nats_user_rule_id ON NatsUsers (nats_user_rule_id); +CREATE UNIQUE INDEX idx_nats_instances_iofog_uuid_unique ON NatsInstances (iofog_uuid); +CREATE INDEX idx_nats_instances_iofog_uuid ON NatsInstances (iofog_uuid); +CREATE UNIQUE INDEX idx_nats_connections_source_dest_unique ON NatsConnections (source_nats, dest_nats); +CREATE INDEX idx_nats_connections_source_nats ON NatsConnections (source_nats); +CREATE INDEX idx_nats_connections_dest_nats ON NatsConnections (dest_nats); +CREATE INDEX idx_nats_account_rules_name ON NatsAccountRules (name); +CREATE INDEX idx_nats_user_rules_name ON NatsUserRules (name); +CREATE INDEX idx_nats_reconcile_tasks_status_claimed ON NatsReconcileTasks (status, claimed_at); + +CREATE INDEX idx_applications_nats_rule_id ON Applications (nats_rule_id); +CREATE INDEX idx_microservices_nats_rule_id ON Microservices (nats_rule_id); +CREATE INDEX idx_microservices_nats_account_id ON Microservices (nats_account_id); +CREATE INDEX idx_microservices_nats_user_id ON Microservices (nats_user_id); + +COMMIT; diff --git a/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql b/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql new file mode 100644 index 00000000..6fa0fdec --- /dev/null +++ b/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql @@ -0,0 +1,966 @@ +CREATE TABLE IF NOT EXISTS "Applications" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR(255) UNIQUE, + description VARCHAR(255) DEFAULT '', + is_activated BOOLEAN DEFAULT false, + is_system BOOLEAN DEFAULT false, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + nats_access BOOLEAN DEFAULT false, + nats_rule_id INTEGER +); + +CREATE TABLE IF NOT EXISTS "Registries" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + url VARCHAR(255), + is_public BOOLEAN, + user_name TEXT, + password TEXT, + user_email TEXT +); + +CREATE TABLE IF NOT EXISTS "CatalogItems" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR(255) UNIQUE, + description VARCHAR(255), + category TEXT, + config_example VARCHAR(255) DEFAULT '{}', + publisher TEXT, + disk_required BIGINT DEFAULT 0, + ram_required BIGINT DEFAULT 0, + picture VARCHAR(255) DEFAULT 'images/shared/default.png', + is_public BOOLEAN DEFAULT false, + registry_id INT, + FOREIGN KEY (registry_id) REFERENCES "Registries" (id) ON DELETE SET NULL +); + +CREATE INDEX idx_catalog_item_registry_id ON "CatalogItems" (registry_id); + +CREATE TABLE IF NOT EXISTS "Architectures" ( + id INT PRIMARY KEY, + name TEXT, + image TEXT, + description TEXT, + network_catalog_item_id INT, + hal_catalog_item_id INT, + bluetooth_catalog_item_id INT, + FOREIGN KEY (network_catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE, + FOREIGN KEY (hal_catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE, + FOREIGN KEY (bluetooth_catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_architecture_network_catalog_item_id ON "Architectures" (network_catalog_item_id); +CREATE INDEX idx_architecture_hal_catalog_item_id ON "Architectures" (hal_catalog_item_id); +CREATE INDEX idx_architecture_bluetooth_catalog_item_id ON "Architectures" (bluetooth_catalog_item_id); + +CREATE TABLE IF NOT EXISTS "Fogs" ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + name VARCHAR(255) DEFAULT 'Unnamed ioFog 1', + location TEXT, + gps_mode TEXT, + latitude DOUBLE PRECISION, + longitude DOUBLE PRECISION, + description TEXT, + last_active BIGINT, + daemon_status VARCHAR(36) DEFAULT 'NOT_PROVISIONED', + daemon_operating_duration BIGINT DEFAULT 0, + daemon_last_start BIGINT, + memory_usage DOUBLE PRECISION DEFAULT 0.000, + disk_usage DOUBLE PRECISION DEFAULT 0.000, + cpu_usage DOUBLE PRECISION DEFAULT 0.00, + memory_violation TEXT, + disk_violation TEXT, + cpu_violation TEXT, + system_available_disk BIGINT, + system_available_memory BIGINT, + system_total_cpu DOUBLE PRECISION, + security_status VARCHAR(36) DEFAULT 'OK', + security_violation_info VARCHAR(36) DEFAULT 'No violation', + catalog_item_status TEXT, + repository_count BIGINT DEFAULT 0, + repository_status TEXT, + system_time BIGINT, + last_status_time BIGINT, + ip_address VARCHAR(36) DEFAULT '0.0.0.0', + ip_address_external VARCHAR(36) DEFAULT '0.0.0.0', + host VARCHAR(36), + catalog_item_message_counts TEXT, + last_command_time BIGINT, + network_interface VARCHAR(36) DEFAULT 'dynamic', + docker_url VARCHAR(255) DEFAULT 'unix:///var/run/docker.sock', + disk_limit DOUBLE PRECISION DEFAULT 50, + disk_directory VARCHAR(255) DEFAULT '/var/lib/iofog-agent/', + memory_limit DOUBLE PRECISION DEFAULT 4096, + cpu_limit DOUBLE PRECISION DEFAULT 80, + log_limit DOUBLE PRECISION DEFAULT 10, + log_directory VARCHAR(255) DEFAULT '/var/log/iofog/', + bluetooth BOOLEAN DEFAULT FALSE, + hal BOOLEAN DEFAULT FALSE, + log_file_count BIGINT DEFAULT 10, + version TEXT, + is_ready_to_upgrade BOOLEAN DEFAULT TRUE, + is_ready_to_rollback BOOLEAN DEFAULT FALSE, + status_frequency INT DEFAULT 10, + change_frequency INT DEFAULT 20, + device_scan_frequency INT DEFAULT 20, + tunnel VARCHAR(255) DEFAULT '', + isolated_docker_container BOOLEAN DEFAULT FALSE, + docker_pruning_freq INT DEFAULT 0, + available_disk_threshold DOUBLE PRECISION DEFAULT 20, + log_level VARCHAR(10) DEFAULT 'INFO', + is_system BOOLEAN DEFAULT FALSE, + router_id INT DEFAULT 0, + time_zone VARCHAR(36) DEFAULT 'Etc/UTC', + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + arch_id INT DEFAULT 0, + container_engine VARCHAR(36), + deployment_type VARCHAR(36), + active_volume_mounts BIGINT DEFAULT 0, + volume_mount_last_update BIGINT DEFAULT 0, + warning_message TEXT DEFAULT 'HEALTHY', + gps_device VARCHAR(36), + gps_scan_frequency INT DEFAULT 60, + edge_guard_frequency INT DEFAULT 0, + gps_status VARCHAR(32), + nats_id INT, + available_runtimes TEXT, + FOREIGN KEY (arch_id) REFERENCES "Architectures" (id) +); + +CREATE INDEX idx_fog_arch_id ON "Fogs" (arch_id); + +CREATE TABLE IF NOT EXISTS "ChangeTrackings" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + microservice_config BOOLEAN DEFAULT false, + reboot BOOLEAN DEFAULT false, + deletenode BOOLEAN DEFAULT false, + version BOOLEAN DEFAULT false, + microservice_list BOOLEAN DEFAULT false, + config BOOLEAN DEFAULT false, + registries BOOLEAN DEFAULT false, + tunnel BOOLEAN DEFAULT false, + diagnostics BOOLEAN DEFAULT false, + router_changed BOOLEAN DEFAULT false, + image_snapshot BOOLEAN DEFAULT false, + prune BOOLEAN DEFAULT false, + last_updated VARCHAR(255) DEFAULT false, + iofog_uuid VARCHAR(36), + volume_mounts BOOLEAN DEFAULT false, + exec_sessions BOOLEAN DEFAULT false, + microservice_logs BOOLEAN DEFAULT false, + fog_logs BOOLEAN DEFAULT false, + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_change_tracking_iofog_uuid ON "ChangeTrackings" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "FogProvisionKeys" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + provisioning_string VARCHAR(100), + expiration_time BIGINT, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_provision_keys_iofogUuid ON "FogProvisionKeys" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "FogVersionCommands" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + version_command VARCHAR(100), + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_version_commands_iofogUuid ON "FogVersionCommands" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "HWInfos" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + info TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_hw_infos_iofogUuid ON "HWInfos" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "USBInfos" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + info TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_usb_infos_iofogUuid ON "USBInfos" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "Tunnels" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + username TEXT, + password TEXT, + host TEXT, + remote_port INT, + local_port INT DEFAULT 22, + rsa_key TEXT, + closed BOOLEAN DEFAULT false, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_tunnels_iofogUuid ON "Tunnels" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "Microservices" ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + config TEXT, + name VARCHAR(255) DEFAULT 'New Microservice', + config_last_updated BIGINT, + rebuild BOOLEAN DEFAULT false, + root_host_access BOOLEAN DEFAULT false, + log_size BIGINT DEFAULT 0, + image_snapshot VARCHAR(255) DEFAULT '', + delete BOOLEAN DEFAULT false, + delete_with_cleanup BOOLEAN DEFAULT false, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + catalog_item_id INT, + registry_id INT DEFAULT 1, + iofog_uuid VARCHAR(36), + application_id INT, + annotations TEXT, + pid_mode VARCHAR(36), + ipc_mode VARCHAR(36), + exec_enabled BOOLEAN DEFAULT false, + schedule INT DEFAULT 50, + cpu_set_cpus TEXT, + memory_limit DOUBLE PRECISION, + is_activated BOOLEAN DEFAULT true, + host_network_mode BOOLEAN DEFAULT false, + is_privileged BOOLEAN DEFAULT false, + nats_rule_id INT, + nats_access BOOLEAN DEFAULT false, + nats_account_id INT, + nats_user_id INT, + nats_creds_secret_name TEXT, + is_controller BOOLEAN DEFAULT false, + FOREIGN KEY (catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE, + FOREIGN KEY (registry_id) REFERENCES "Registries" (id) ON DELETE SET NULL, + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES "Applications" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_microservices_catalogItemId ON "Microservices" (catalog_item_id); +CREATE INDEX idx_microservices_registryId ON "Microservices" (registry_id); +CREATE INDEX idx_microservices_iofogUuid ON "Microservices" (iofog_uuid); +CREATE INDEX idx_microservices_applicationId ON "Microservices" (application_id); + +CREATE TABLE IF NOT EXISTS "MicroserviceArgs" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + cmd TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_args_microserviceUuid ON "MicroserviceArgs" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "MicroserviceEnvs" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + key TEXT, + value TEXT, + microservice_uuid VARCHAR(36), + value_from_secret TEXT, + value_from_config_map TEXT, + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_envs_microserviceUuid ON "MicroserviceEnvs" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "MicroserviceExtraHost" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + template_type TEXT, + name TEXT, + public_port INT, + template TEXT, + value TEXT, + microservice_uuid VARCHAR(36), + target_microservice_uuid VARCHAR(36), + target_fog_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE, + FOREIGN KEY (target_microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE, + FOREIGN KEY (target_fog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_extra_host_microserviceUuid ON "MicroserviceExtraHost" (microservice_uuid); +CREATE INDEX idx_microservice_extra_host_targetMicroserviceUuid ON "MicroserviceExtraHost" (target_microservice_uuid); +CREATE INDEX idx_microservice_extra_host_targetFogUuid ON "MicroserviceExtraHost" (target_fog_uuid); + +CREATE TABLE IF NOT EXISTS "MicroservicePorts" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + port_internal INT, + port_external INT, + is_udp BOOLEAN, + is_public BOOLEAN, + is_proxy BOOLEAN, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_port_microserviceUuid ON "MicroservicePorts" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "MicroserviceStatuses" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + status VARCHAR(255) DEFAULT 'QUEUED', + operating_duration BIGINT DEFAULT 0, + start_time BIGINT DEFAULT 0, + cpu_usage DOUBLE PRECISION DEFAULT 0.000, + memory_usage BIGINT DEFAULT 0, + container_id VARCHAR(255) DEFAULT '', + percentage DOUBLE PRECISION DEFAULT 0.00, + error_message TEXT, + microservice_uuid VARCHAR(36), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + ip_address TEXT, + exec_session_ids TEXT, + health_status TEXT, + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_status_microserviceUuid ON "MicroserviceStatuses" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "VolumeMappings" ( + uuid INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + host_destination TEXT, + container_destination TEXT, + access_mode TEXT, + type TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_volume_mappings_microserviceUuid ON "VolumeMappings" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "CatalogItemImages" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + container_image TEXT, + catalog_item_id INT, + microservice_uuid VARCHAR(36), + arch_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE, + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE, + FOREIGN KEY (arch_id) REFERENCES "Architectures" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_image_catalog_item_id ON "CatalogItemImages" (catalog_item_id); +CREATE INDEX idx_catalog_item_image_microservice_uuid ON "CatalogItemImages" (microservice_uuid); +CREATE INDEX idx_catalog_item_image_arch_id ON "CatalogItemImages" (arch_id); + +CREATE TABLE IF NOT EXISTS "CatalogItemInputTypes" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + info_type TEXT, + info_format TEXT, + catalog_item_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_input_type_catalog_item_id ON "CatalogItemInputTypes" (catalog_item_id); + +CREATE TABLE IF NOT EXISTS "CatalogItemOutputTypes" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + info_type TEXT, + info_format TEXT, + catalog_item_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_output_type_catalog_item_id ON "CatalogItemOutputTypes" (catalog_item_id); + +CREATE TABLE IF NOT EXISTS "Routers" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + is_edge BOOLEAN DEFAULT true, + messaging_port INT DEFAULT 5671, + edge_router_port INT, + inter_router_port INT, + host TEXT, + is_default BOOLEAN DEFAULT false, + iofog_uuid VARCHAR(36), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_router_iofogUuid ON "Routers" (iofog_uuid); + +CREATE TABLE "RouterConnections" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + source_router INT, + dest_router INT, + created_at TIMESTAMP(0) NOT NULL, + updated_at TIMESTAMP(0) NOT NULL, + FOREIGN KEY (source_router) REFERENCES "Routers"(id) ON DELETE CASCADE, + FOREIGN KEY (dest_router) REFERENCES "Routers"(id) ON DELETE CASCADE +); + +CREATE INDEX idx_routerconnections_sourceRouter ON "RouterConnections" (source_router); +CREATE INDEX idx_routerconnections_destRouter ON "RouterConnections" (dest_router); + +CREATE TABLE IF NOT EXISTS "Config" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + key VARCHAR(255) NOT NULL UNIQUE, + value VARCHAR(255) NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_config_key ON "Config" (key); + +CREATE TABLE IF NOT EXISTS "Tags" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + value VARCHAR(255) UNIQUE NOT NULL +); + +CREATE TABLE IF NOT EXISTS "IofogTags" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + fog_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (fog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES "Tags" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_iofogtags_fog_uuid ON "IofogTags" (fog_uuid); +CREATE INDEX idx_iofogtags_tag_id ON "IofogTags" (tag_id); + +CREATE TABLE IF NOT EXISTS "ApplicationTemplates" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL DEFAULT 'new-application', + description VARCHAR(255) DEFAULT '', + schema_version VARCHAR(255) DEFAULT '', + application_json TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "ApplicationTemplateVariables" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + application_template_id INT NOT NULL, + key TEXT, + description VARCHAR(255) DEFAULT '', + default_value VARCHAR(255), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (application_template_id) REFERENCES "ApplicationTemplates" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_applicationtemplatevariables_application_template_id ON "ApplicationTemplateVariables" (application_template_id); + +CREATE TABLE IF NOT EXISTS "MicroserviceCdiDevices" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + cdi_devices TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_cdiDevices_microserviceUuid ON "MicroserviceCdiDevices" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "MicroservicePubTags" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + microservice_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES "Tags" (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS "MicroserviceSubTags" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + microservice_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES "Tags" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_microservicepubtags_microservice_uuid ON "MicroservicePubTags" (microservice_uuid); +CREATE INDEX idx_microservicesubtags_microservice_uuid ON "MicroserviceSubTags" (microservice_uuid); +CREATE INDEX idx_microservicepubtags_tag_id ON "MicroservicePubTags" (tag_id); +CREATE INDEX idx_microservicesubtags_tag_id ON "MicroserviceSubTags" (tag_id); + +CREATE TABLE IF NOT EXISTS "MicroserviceCapAdd" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + cap_add TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_capAdd_microserviceUuid ON "MicroserviceCapAdd" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "MicroserviceCapDrop" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + cap_drop TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_capDrop_microserviceUuid ON "MicroserviceCapDrop" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "FogPublicKeys" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + public_key TEXT, + iofog_uuid VARCHAR(36), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_public_keys_iofogUuid ON "FogPublicKeys" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "FogUsedTokens" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + jti VARCHAR(255) NOT NULL, + iofog_uuid VARCHAR(36), + expiry_time BIGINT NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_used_tokens_iofogUuid ON "FogUsedTokens" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "Secrets" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + type VARCHAR(50) NOT NULL CHECK (type IN ('Opaque', 'tls')), + data TEXT NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_secrets_name ON "Secrets" (name); + +CREATE TABLE IF NOT EXISTS "Certificates" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT UNIQUE NOT NULL, + subject TEXT NOT NULL, + is_ca BOOLEAN DEFAULT false, + signed_by_id INT, + hosts TEXT, + valid_from TIMESTAMP(0) NOT NULL, + valid_to TIMESTAMP(0) NOT NULL, + serial_number TEXT NOT NULL, + secret_id INT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (signed_by_id) REFERENCES "Certificates" (id) ON DELETE SET NULL, + FOREIGN KEY (secret_id) REFERENCES "Secrets" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_certificates_name ON "Certificates" (name); +CREATE INDEX idx_certificates_valid_to ON "Certificates" (valid_to); +CREATE INDEX idx_certificates_is_ca ON "Certificates" (is_ca); +CREATE INDEX idx_certificates_signed_by_id ON "Certificates" (signed_by_id); +CREATE INDEX idx_certificates_secret_id ON "Certificates" (secret_id); + +CREATE TABLE IF NOT EXISTS "Services" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT UNIQUE NOT NULL, + type TEXT NOT NULL, + resource TEXT NOT NULL, + target_port INTEGER NOT NULL, + service_port INTEGER, + k8s_type TEXT, + bridge_port INTEGER, + default_bridge TEXT, + service_endpoint TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + provisioning_status VARCHAR(36) DEFAULT 'pending', + provisioning_error TEXT +); + +CREATE INDEX idx_services_name ON "Services" (name); +CREATE INDEX idx_services_id ON "Services" (id); + +CREATE TABLE IF NOT EXISTS "ServiceTags" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + service_id INTEGER NOT NULL, + tag_id INTEGER NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (service_id) REFERENCES "Services" (id) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES "Tags" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_service_tags_service_id ON "ServiceTags" (service_id); +CREATE INDEX idx_service_tags_tag_id ON "ServiceTags" (tag_id); + +CREATE TABLE IF NOT EXISTS "ConfigMaps" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + immutable BOOLEAN DEFAULT false, + data TEXT NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + use_vault BOOLEAN DEFAULT true +); + +CREATE INDEX idx_config_maps_name ON "ConfigMaps" (name); + +CREATE TABLE IF NOT EXISTS "VolumeMounts" ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + name VARCHAR(255) NOT NULL, + config_map_name VARCHAR(255), + secret_name VARCHAR(255), + version INT DEFAULT 1, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (config_map_name) REFERENCES "ConfigMaps" (name) ON DELETE CASCADE, + FOREIGN KEY (secret_name) REFERENCES "Secrets" (name) ON DELETE CASCADE +); + +CREATE INDEX idx_volume_mounts_uuid ON "VolumeMounts" (uuid); +CREATE INDEX idx_volume_mounts_config_map_name ON "VolumeMounts" (config_map_name); +CREATE INDEX idx_volume_mounts_secret_name ON "VolumeMounts" (secret_name); + +CREATE TABLE IF NOT EXISTS "FogVolumeMounts" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + fog_uuid VARCHAR(36), + volume_mount_uuid VARCHAR(36), + FOREIGN KEY (fog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE, + FOREIGN KEY (volume_mount_uuid) REFERENCES "VolumeMounts" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_volume_mounts_fog_uuid ON "FogVolumeMounts" (fog_uuid); +CREATE INDEX idx_fog_volume_mounts_volume_mount_uuid ON "FogVolumeMounts" (volume_mount_uuid); + +CREATE TABLE IF NOT EXISTS "MicroserviceExecStatuses" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + status VARCHAR(255) DEFAULT 'INACTIVE', + exec_session_id VARCHAR(255), + microservice_uuid VARCHAR(36), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_exec_status_microservice_uuid ON "MicroserviceExecStatuses" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "MicroserviceHealthChecks" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + test TEXT, + interval DOUBLE PRECISION, + timeout DOUBLE PRECISION, + start_period DOUBLE PRECISION, + start_interval DOUBLE PRECISION, + retries INT, + microservice_uuid VARCHAR(36), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_health_check_microservice_uuid ON "MicroserviceHealthChecks" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "Events" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + timestamp BIGINT NOT NULL, + event_type VARCHAR(20) NOT NULL, + endpoint_type VARCHAR(10) NOT NULL, + actor_id VARCHAR(255), + method VARCHAR(10), + resource_type VARCHAR(50), + resource_id VARCHAR(255), + endpoint_path TEXT NOT NULL, + ip_address VARCHAR(45), + status VARCHAR(20) NOT NULL, + status_code INT, + status_message TEXT, + request_id VARCHAR(255), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_events_timestamp ON "Events" (timestamp); +CREATE INDEX idx_events_endpoint_type ON "Events" (endpoint_type); +CREATE INDEX idx_events_actor_id ON "Events" (actor_id); +CREATE INDEX idx_events_resource_type ON "Events" (resource_type); +CREATE INDEX idx_events_status ON "Events" (status); +CREATE INDEX idx_events_method ON "Events" (method); +CREATE INDEX idx_events_event_type ON "Events" (event_type); +CREATE INDEX idx_events_created_at ON "Events" (created_at); + +CREATE TABLE IF NOT EXISTS "MicroserviceLogStatuses" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + microservice_uuid VARCHAR(36), + log_session_id TEXT, + session_id VARCHAR(255) UNIQUE NOT NULL, + status TEXT, + tail_config TEXT, + agent_connected BOOLEAN DEFAULT false, + user_connected BOOLEAN DEFAULT false, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_log_status_microservice_uuid ON "MicroserviceLogStatuses" (microservice_uuid); +CREATE INDEX idx_microservice_log_status_session_id ON "MicroserviceLogStatuses" (session_id); + +CREATE TABLE IF NOT EXISTS "FogLogStatuses" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + iofog_uuid VARCHAR(36), + log_session_id TEXT, + session_id VARCHAR(255) UNIQUE NOT NULL, + status TEXT, + tail_config TEXT, + agent_connected BOOLEAN DEFAULT false, + user_connected BOOLEAN DEFAULT false, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_log_status_iofog_uuid ON "FogLogStatuses" (iofog_uuid); +CREATE INDEX idx_fog_log_status_session_id ON "FogLogStatuses" (session_id); + +CREATE TABLE IF NOT EXISTS "RbacRoles" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + kind TEXT DEFAULT 'Role', + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "RbacRoleRules" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + role_id INT NOT NULL, + api_groups TEXT NOT NULL, + resources TEXT NOT NULL, + verbs TEXT NOT NULL, + resource_names TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (role_id) REFERENCES "RbacRoles" (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS "RbacRoleBindings" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + kind TEXT DEFAULT 'RoleBinding', + role_ref TEXT NOT NULL, + subjects TEXT NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + role_id INTEGER +); + +CREATE TABLE IF NOT EXISTS "RbacServiceAccounts" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name TEXT NOT NULL, + role_ref TEXT, + role_id INT REFERENCES "RbacRoles" (id), + microservice_uuid VARCHAR(36) REFERENCES "Microservices" (uuid) ON DELETE CASCADE, + application_id INT REFERENCES "Applications" (id) ON DELETE SET NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_rbac_role_rules_role_id ON "RbacRoleRules" (role_id); +CREATE INDEX idx_rbac_roles_name ON "RbacRoles" (name); +CREATE INDEX idx_rbac_role_bindings_name ON "RbacRoleBindings" (name); +CREATE INDEX idx_rbac_service_accounts_name ON "RbacServiceAccounts" (name); + +CREATE TABLE IF NOT EXISTS "RbacCacheVersion" ( + id INT PRIMARY KEY DEFAULT 1, + version BIGINT NOT NULL DEFAULT 1, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + CONSTRAINT single_row CHECK (id = 1) +); + +CREATE INDEX idx_rbac_role_bindings_role_id ON "RbacRoleBindings" (role_id); + +CREATE INDEX idx_rbac_service_accounts_role_id ON "RbacServiceAccounts" (role_id); +CREATE UNIQUE INDEX idx_rbac_service_accounts_microservice_uuid_unique ON "RbacServiceAccounts" (microservice_uuid) WHERE microservice_uuid IS NOT NULL; +CREATE UNIQUE INDEX idx_rbac_service_accounts_application_id_name_unique ON "RbacServiceAccounts" (application_id, name); + +CREATE TABLE IF NOT EXISTS "ClusterControllers" ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + host VARCHAR(255), + process_id INT, + last_heartbeat TIMESTAMP(0), + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_cluster_controllers_uuid ON "ClusterControllers" (uuid); +CREATE INDEX idx_cluster_controllers_host ON "ClusterControllers" (host); +CREATE INDEX idx_cluster_controllers_active ON "ClusterControllers" (is_active, last_heartbeat); + +CREATE TABLE IF NOT EXISTS "NatsOperators" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT UNIQUE NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + seed_secret_name TEXT NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "NatsAccounts" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + seed_secret_name TEXT NOT NULL, + is_system BOOLEAN DEFAULT false, + is_leaf_system BOOLEAN DEFAULT false, + operator_id INT NOT NULL, + application_id INT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (operator_id) REFERENCES "NatsOperators" (id) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES "Applications" (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS "NatsUsers" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + creds_secret_name TEXT NOT NULL, + is_bearer BOOLEAN DEFAULT false, + account_id INT NOT NULL, + microservice_uuid VARCHAR(36), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + nats_user_rule_id INTEGER, + FOREIGN KEY (account_id) REFERENCES "NatsAccounts" (id) ON DELETE CASCADE, + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS "NatsInstances" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + iofog_uuid VARCHAR(36), + is_leaf BOOLEAN DEFAULT true, + is_hub BOOLEAN DEFAULT false, + host TEXT, + server_port INT, + leaf_port INT, + cluster_port INT, + mqtt_port INT, + http_port INT, + configmap_name TEXT, + jwt_dir_mount_name TEXT, + cert_secret_name TEXT, + js_storage_size TEXT, + js_memory_store_size TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS "NatsConnections" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + source_nats INT NOT NULL, + dest_nats INT NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (source_nats) REFERENCES "NatsInstances" (id) ON DELETE CASCADE, + FOREIGN KEY (dest_nats) REFERENCES "NatsInstances" (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS "NatsReconcileTasks" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + reason VARCHAR(64) NOT NULL, + application_id INT, + account_rule_id INT, + user_rule_id INT, + fog_uuids TEXT, + status VARCHAR(32) NOT NULL DEFAULT 'pending', + leader_uuid VARCHAR(36), + claimed_at TIMESTAMP(0), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "NatsAccountRules" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT UNIQUE NOT NULL, + description TEXT, + info_url TEXT, + max_connections INT, + max_leaf_node_connections INT, + max_data BIGINT, + max_exports INT, + max_imports INT, + max_msg_payload INT, + max_subscriptions INT, + exports_allow_wildcards BOOLEAN DEFAULT true, + disallow_bearer BOOLEAN, + response_permissions TEXT, + resp_max INT, + resp_ttl BIGINT, + imports TEXT, + exports TEXT, + mem_storage BIGINT, + disk_storage BIGINT, + streams INT, + consumer INT, + max_ack_pending INT, + mem_max_stream_bytes BIGINT, + disk_max_stream_bytes BIGINT, + max_bytes_required BOOLEAN, + tiered_limits TEXT, + pub_allow TEXT, + pub_deny TEXT, + sub_allow TEXT, + sub_deny TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "NatsUserRules" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT UNIQUE NOT NULL, + description TEXT, + max_subscriptions INT, + max_payload INT, + max_data BIGINT, + bearer_token BOOLEAN DEFAULT false, + proxy_required BOOLEAN, + allowed_connection_types TEXT, + src TEXT, + times TEXT, + times_location TEXT, + resp_max INT, + resp_ttl BIGINT, + pub_allow TEXT, + pub_deny TEXT, + sub_allow TEXT, + sub_deny TEXT, + tags TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE UNIQUE INDEX idx_nats_accounts_application_id_unique ON "NatsAccounts" (application_id) WHERE application_id IS NOT NULL; +CREATE INDEX idx_nats_accounts_application_id ON "NatsAccounts" (application_id); +CREATE UNIQUE INDEX idx_nats_users_account_id_name ON "NatsUsers" (account_id, name); +CREATE INDEX idx_nats_users_account_id ON "NatsUsers" (account_id); +CREATE INDEX idx_nats_users_microservice_uuid ON "NatsUsers" (microservice_uuid); +CREATE INDEX idx_nats_users_nats_user_rule_id ON "NatsUsers" (nats_user_rule_id); +CREATE UNIQUE INDEX idx_nats_instances_iofog_uuid_unique ON "NatsInstances" (iofog_uuid); +CREATE INDEX idx_nats_instances_iofog_uuid ON "NatsInstances" (iofog_uuid); +CREATE UNIQUE INDEX idx_nats_connections_source_dest_unique ON "NatsConnections" (source_nats, dest_nats); +CREATE INDEX idx_nats_connections_source_nats ON "NatsConnections" (source_nats); +CREATE INDEX idx_nats_connections_dest_nats ON "NatsConnections" (dest_nats); +CREATE INDEX idx_nats_account_rules_name ON "NatsAccountRules" (name); +CREATE INDEX idx_nats_user_rules_name ON "NatsUserRules" (name); +CREATE INDEX idx_nats_reconcile_tasks_status_claimed ON "NatsReconcileTasks" (status, claimed_at); + +CREATE INDEX idx_applications_nats_rule_id ON "Applications" (nats_rule_id); +CREATE INDEX idx_microservices_nats_rule_id ON "Microservices" (nats_rule_id); +CREATE INDEX idx_microservices_nats_account_id ON "Microservices" (nats_account_id); +CREATE INDEX idx_microservices_nats_user_id ON "Microservices" (nats_user_id); diff --git a/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql b/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql new file mode 100644 index 00000000..2f141d98 --- /dev/null +++ b/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql @@ -0,0 +1,965 @@ +CREATE TABLE IF NOT EXISTS Applications ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name VARCHAR(255) UNIQUE, + description VARCHAR(255) DEFAULT '', + is_activated BOOLEAN DEFAULT false, + is_system BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + nats_access BOOLEAN DEFAULT false, + nats_rule_id INTEGER +); + +CREATE TABLE IF NOT EXISTS Registries ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + url VARCHAR(255), + is_public BOOLEAN, + user_name TEXT, + password TEXT, + user_email TEXT +); + +CREATE TABLE IF NOT EXISTS CatalogItems ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name VARCHAR(255) UNIQUE, + description VARCHAR(255), + category TEXT, + config_example VARCHAR(255) DEFAULT '{}', + publisher TEXT, + disk_required BIGINT DEFAULT 0, + ram_required BIGINT DEFAULT 0, + picture VARCHAR(255) DEFAULT 'images/shared/default.png', + is_public BOOLEAN DEFAULT false, + registry_id INT, + FOREIGN KEY (registry_id) REFERENCES Registries (id) ON DELETE SET NULL +); + +CREATE INDEX idx_catalog_item_registry_id ON CatalogItems (registry_id); + +CREATE TABLE IF NOT EXISTS Architectures ( + id INT PRIMARY KEY, + name TEXT, + image TEXT, + description TEXT, + network_catalog_item_id INT, + hal_catalog_item_id INT, + bluetooth_catalog_item_id INT, + FOREIGN KEY (network_catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (hal_catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (bluetooth_catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE +); + +CREATE INDEX idx_architecture_network_catalog_item_id ON Architectures (network_catalog_item_id); +CREATE INDEX idx_architecture_hal_catalog_item_id ON Architectures (hal_catalog_item_id); +CREATE INDEX idx_architecture_bluetooth_catalog_item_id ON Architectures (bluetooth_catalog_item_id); + +CREATE TABLE IF NOT EXISTS Fogs ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + name VARCHAR(255) DEFAULT 'Unnamed ioFog 1', + location TEXT, + gps_mode TEXT, + latitude FLOAT, + longitude FLOAT, + description TEXT, + last_active BIGINT, + daemon_status VARCHAR(36) DEFAULT 'NOT_PROVISIONED', + daemon_operating_duration BIGINT DEFAULT 0, + daemon_last_start BIGINT, + memory_usage FLOAT DEFAULT 0.000, + disk_usage FLOAT DEFAULT 0.000, + cpu_usage FLOAT DEFAULT 0.00, + memory_violation TEXT, + disk_violation TEXT, + cpu_violation TEXT, + system_available_disk BIGINT, + system_available_memory BIGINT, + system_total_cpu FLOAT, + security_status VARCHAR(36) DEFAULT 'OK', + security_violation_info VARCHAR(36) DEFAULT 'No violation', + catalog_item_status TEXT, + repository_count BIGINT DEFAULT 0, + repository_status TEXT, + system_time BIGINT, + last_status_time BIGINT, + ip_address VARCHAR(36) DEFAULT '0.0.0.0', + ip_address_external VARCHAR(36) DEFAULT '0.0.0.0', + host VARCHAR(36), + catalog_item_message_counts TEXT, + last_command_time BIGINT, + network_interface VARCHAR(36) DEFAULT 'dynamic', + docker_url VARCHAR(255) DEFAULT 'unix:///var/run/docker.sock', + disk_limit FLOAT DEFAULT 50, + disk_directory VARCHAR(255) DEFAULT '/var/lib/iofog-agent/', + memory_limit FLOAT DEFAULT 4096, + cpu_limit FLOAT DEFAULT 80, + log_limit FLOAT DEFAULT 10, + log_directory VARCHAR(255) DEFAULT '/var/log/iofog-agent/', + bluetooth BOOLEAN DEFAULT FALSE, + hal BOOLEAN DEFAULT FALSE, + log_file_count BIGINT DEFAULT 10, + `version` TEXT, + is_ready_to_upgrade BOOLEAN DEFAULT TRUE, + is_ready_to_rollback BOOLEAN DEFAULT FALSE, + status_frequency INT DEFAULT 10, + change_frequency INT DEFAULT 20, + device_scan_frequency INT DEFAULT 20, + tunnel VARCHAR(255) DEFAULT '', + isolated_docker_container BOOLEAN DEFAULT FALSE, + docker_pruning_freq INT DEFAULT 0, + available_disk_threshold FLOAT DEFAULT 20, + log_level VARCHAR(10) DEFAULT 'INFO', + is_system BOOLEAN DEFAULT FALSE, + router_id INT DEFAULT 0, + time_zone VARCHAR(36) DEFAULT 'Etc/UTC', + created_at DATETIME, + updated_at DATETIME, + arch_id INT DEFAULT 0, + container_engine VARCHAR(36), + deployment_type VARCHAR(36), + active_volume_mounts BIGINT DEFAULT 0, + volume_mount_last_update BIGINT DEFAULT 0, + warning_message TEXT DEFAULT 'HEALTHY', + gps_device VARCHAR(36), + gps_scan_frequency INT DEFAULT 60, + edge_guard_frequency INT DEFAULT 0, + gps_status VARCHAR(32), + nats_id INT, + available_runtimes TEXT, + FOREIGN KEY (arch_id) REFERENCES Architectures (id) +); + +CREATE INDEX idx_fog_arch_id ON Fogs (arch_id); + +CREATE TABLE IF NOT EXISTS ChangeTrackings ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + microservice_config BOOLEAN DEFAULT false, + reboot BOOLEAN DEFAULT false, + deletenode BOOLEAN DEFAULT false, + version BOOLEAN DEFAULT false, + microservice_list BOOLEAN DEFAULT false, + config BOOLEAN DEFAULT false, + registries BOOLEAN DEFAULT false, + tunnel BOOLEAN DEFAULT false, + diagnostics BOOLEAN DEFAULT false, + router_changed BOOLEAN DEFAULT false, + image_snapshot BOOLEAN DEFAULT false, + prune BOOLEAN DEFAULT false, + last_updated VARCHAR(255) DEFAULT false, + iofog_uuid VARCHAR(36), + volume_mounts BOOLEAN DEFAULT false, + exec_sessions BOOLEAN DEFAULT false, + microservice_logs BOOLEAN DEFAULT false, + fog_logs BOOLEAN DEFAULT false, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_change_tracking_iofog_uuid ON ChangeTrackings (iofog_uuid); + +CREATE TABLE IF NOT EXISTS FogProvisionKeys ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + provisioning_string VARCHAR(100), + expiration_time BIGINT, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_provision_keys_iofogUuid ON FogProvisionKeys (iofog_uuid); + +CREATE TABLE IF NOT EXISTS FogVersionCommands ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + version_command VARCHAR(100), + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_version_commands_iofogUuid ON FogVersionCommands (iofog_uuid); + +CREATE TABLE IF NOT EXISTS HWInfos ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + info TEXT, + created_at DATETIME, + updated_at DATETIME, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_hw_infos_iofogUuid ON HWInfos (iofog_uuid); + +CREATE TABLE IF NOT EXISTS USBInfos ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + info TEXT, + created_at DATETIME, + updated_at DATETIME, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_usb_infos_iofogUuid ON USBInfos (iofog_uuid); + +CREATE TABLE IF NOT EXISTS Tunnels ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + username TEXT, + password TEXT, + host TEXT, + remote_port INT, + local_port INT DEFAULT 22, + rsa_key TEXT, + closed BOOLEAN DEFAULT false, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_tunnels_iofogUuid ON Tunnels (iofog_uuid); + +CREATE TABLE IF NOT EXISTS Microservices ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + config TEXT, + name VARCHAR(255) DEFAULT 'New Microservice', + config_last_updated BIGINT, + rebuild BOOLEAN DEFAULT false, + root_host_access BOOLEAN DEFAULT false, + log_size BIGINT DEFAULT 0, + image_snapshot VARCHAR(255) DEFAULT '', + `delete` BOOLEAN DEFAULT false, + delete_with_cleanup BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + catalog_item_id INT, + registry_id INT DEFAULT 1, + iofog_uuid VARCHAR(36), + application_id INT, + run_as_user TEXT, + platform TEXT, + runtime TEXT, + annotations TEXT, + pid_mode VARCHAR(36), + ipc_mode VARCHAR(36), + exec_enabled BOOLEAN DEFAULT false, + schedule INT DEFAULT 50, + cpu_set_cpus TEXT, + memory_limit FLOAT, + is_activated BOOLEAN DEFAULT true, + host_network_mode BOOLEAN DEFAULT false, + is_privileged BOOLEAN DEFAULT false, + nats_rule_id INTEGER, + nats_access BOOLEAN DEFAULT false, + nats_account_id INTEGER, + nats_user_id INTEGER, + nats_creds_secret_name TEXT, + is_controller BOOLEAN DEFAULT false, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (registry_id) REFERENCES Registries (id) ON DELETE SET NULL, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES Applications (id) ON DELETE CASCADE +); + +CREATE INDEX idx_microservices_catalogItemId ON Microservices (catalog_item_id); +CREATE INDEX idx_microservices_registryId ON Microservices (registry_id); +CREATE INDEX idx_microservices_iofogUuid ON Microservices (iofog_uuid); +CREATE INDEX idx_microservices_applicationId ON Microservices (application_id); + +CREATE TABLE IF NOT EXISTS MicroserviceArgs ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + cmd TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_args_microserviceUuid ON MicroserviceArgs (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceEnvs ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `key` TEXT, + `value` TEXT, + microservice_uuid VARCHAR(36), + value_from_secret TEXT, + value_from_config_map TEXT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_envs_microserviceUuid ON MicroserviceEnvs (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceExtraHost ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + template_type TEXT, + name TEXT, + template TEXT, + `value` TEXT, + microservice_uuid VARCHAR(36), + target_microservice_uuid VARCHAR(36), + target_fog_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (target_microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (target_fog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_extra_host_microserviceUuid ON MicroserviceExtraHost (microservice_uuid); +CREATE INDEX idx_microservice_extra_host_targetMicroserviceUuid ON MicroserviceExtraHost (target_microservice_uuid); +CREATE INDEX idx_microservice_extra_host_targetFogUuid ON MicroserviceExtraHost (target_fog_uuid); + +CREATE TABLE IF NOT EXISTS MicroservicePorts ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + port_internal INT, + port_external INT, + is_udp BOOLEAN, + created_at DATETIME, + updated_at DATETIME, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_port_microserviceUuid ON MicroservicePorts (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceStatuses ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + status VARCHAR(255) DEFAULT 'QUEUED', + operating_duration BIGINT DEFAULT 0, + start_time BIGINT DEFAULT 0, + cpu_usage FLOAT DEFAULT 0.000, + memory_usage BIGINT DEFAULT 0, + container_id VARCHAR(255) DEFAULT '', + percentage FLOAT DEFAULT 0.00, + error_message TEXT, + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + ip_address TEXT, + exec_session_ids TEXT, + health_status TEXT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_status_microserviceUuid ON MicroserviceStatuses (microservice_uuid); + +CREATE TABLE IF NOT EXISTS VolumeMappings ( + uuid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + host_destination TEXT, + container_destination TEXT, + access_mode TEXT, + type TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_volume_mappings_microserviceUuid ON VolumeMappings (microservice_uuid); + +CREATE TABLE IF NOT EXISTS CatalogItemImages ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + container_image TEXT, + catalog_item_id INT, + microservice_uuid VARCHAR(36), + arch_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (arch_id) REFERENCES Architectures (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_image_catalog_item_id ON CatalogItemImages (catalog_item_id); +CREATE INDEX idx_catalog_item_image_microservice_uuid ON CatalogItemImages (microservice_uuid); +CREATE INDEX idx_catalog_item_image_arch_id ON CatalogItemImages (arch_id); + +CREATE TABLE IF NOT EXISTS CatalogItemInputTypes ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + info_type TEXT, + info_format TEXT, + catalog_item_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_input_type_catalog_item_id ON CatalogItemInputTypes (catalog_item_id); + +CREATE TABLE IF NOT EXISTS CatalogItemOutputTypes ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + info_type TEXT, + info_format TEXT, + catalog_item_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_output_type_catalog_item_id ON CatalogItemOutputTypes (catalog_item_id); + +CREATE TABLE IF NOT EXISTS Routers ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + is_edge BOOLEAN DEFAULT true, + messaging_port INT DEFAULT 5671, + edge_router_port INT, + inter_router_port INT, + host TEXT, + is_default BOOLEAN DEFAULT false, + iofog_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_router_iofogUuid ON Routers (iofog_uuid); + +CREATE TABLE RouterConnections ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + source_router INT, + dest_router INT, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (source_router) REFERENCES Routers(id) ON DELETE CASCADE, + FOREIGN KEY (dest_router) REFERENCES Routers(id) ON DELETE CASCADE +); + +CREATE INDEX idx_routerconnections_sourceRouter ON RouterConnections (source_router); +CREATE INDEX idx_routerconnections_destRouter ON RouterConnections (dest_router); + +CREATE TABLE IF NOT EXISTS Config ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `key` VARCHAR(255) NOT NULL UNIQUE, + value VARCHAR(255) NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_config_key ON Config (`key`); + +CREATE TABLE IF NOT EXISTS Tags ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + value VARCHAR(255) UNIQUE NOT NULL +); + +CREATE TABLE IF NOT EXISTS IofogTags ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + fog_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (fog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE INDEX idx_iofogtags_fog_uuid ON IofogTags (fog_uuid); +CREATE INDEX idx_iofogtags_tag_id ON IofogTags (tag_id); + +CREATE TABLE IF NOT EXISTS ApplicationTemplates ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL DEFAULT 'new-application', + description VARCHAR(255) DEFAULT '', + schema_version VARCHAR(255) DEFAULT '', + application_json LONGTEXT, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS ApplicationTemplateVariables ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + application_template_id INT NOT NULL, + `key` TEXT, + description VARCHAR(255) DEFAULT '', + default_value VARCHAR(255), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (application_template_id) REFERENCES ApplicationTemplates (id) ON DELETE CASCADE +); + +CREATE INDEX idx_applicationtemplatevariables_application_template_id ON ApplicationTemplateVariables (application_template_id); + +CREATE TABLE IF NOT EXISTS MicroserviceCdiDevices ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + cdi_devices TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_cdiDevices_microserviceUuid ON MicroserviceCdiDevices (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroservicePubTags ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + microservice_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS MicroserviceSubTags ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + microservice_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE INDEX idx_microservicepubtags_microservice_uuid ON MicroservicePubTags (microservice_uuid); +CREATE INDEX idx_microservicesubtags_microservice_uuid ON MicroservicesubTags (microservice_uuid); +CREATE INDEX idx_microservicepubtags_tag_id ON MicroservicePubTags (tag_id); +CREATE INDEX idx_microservicesubtags_tag_id ON MicroservicesubTags (tag_id); + +CREATE TABLE IF NOT EXISTS MicroserviceCapAdd ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + cap_add TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_capAdd_microserviceUuid ON MicroserviceCapAdd (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceCapDrop ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + cap_drop TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_capDrop_microserviceUuid ON MicroserviceCapDrop (microservice_uuid); + +CREATE TABLE IF NOT EXISTS FogPublicKeys ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + public_key TEXT, + iofog_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_public_keys_iofogUuid ON FogPublicKeys (iofog_uuid); + +CREATE TABLE IF NOT EXISTS FogUsedTokens ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + jti VARCHAR(255) NOT NULL, + iofog_uuid VARCHAR(36), + expiry_time BIGINT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_used_tokens_iofogUuid ON FogUsedTokens (iofog_uuid); + +CREATE TABLE IF NOT EXISTS Secrets ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + type VARCHAR(50) NOT NULL CHECK (type IN ('Opaque', 'tls')), + data TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_secrets_name ON Secrets (name); + +CREATE TABLE IF NOT EXISTS Certificates ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + subject TEXT NOT NULL, + is_ca BOOLEAN DEFAULT false, + signed_by_id INTEGER, + hosts TEXT, + valid_from DATETIME NOT NULL, + valid_to DATETIME NOT NULL, + serial_number TEXT NOT NULL, + secret_id INTEGER, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (signed_by_id) REFERENCES Certificates (id) ON DELETE SET NULL, + FOREIGN KEY (secret_id) REFERENCES Secrets (id) ON DELETE CASCADE +); + +CREATE INDEX idx_certificates_name ON Certificates (name); +CREATE INDEX idx_certificates_valid_to ON Certificates (valid_to); +CREATE INDEX idx_certificates_is_ca ON Certificates (is_ca); +CREATE INDEX idx_certificates_signed_by_id ON Certificates (signed_by_id); +CREATE INDEX idx_certificates_secret_id ON Certificates (secret_id); + +CREATE TABLE IF NOT EXISTS Services ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + type TEXT NOT NULL, + resource TEXT NOT NULL, + target_port INTEGER NOT NULL, + service_port INTEGER, + k8s_type TEXT, + bridge_port INTEGER, + default_bridge TEXT, + service_endpoint TEXT, + created_at DATETIME, + updated_at DATETIME, + provisioning_status VARCHAR(36) DEFAULT 'pending', + provisioning_error TEXT +); + +CREATE INDEX idx_services_id ON Services (id); +CREATE INDEX idx_services_name ON Services (name); + +CREATE TABLE IF NOT EXISTS ServiceTags ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + service_id INTEGER NOT NULL, + tag_id INTEGER NOT NULL, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (service_id) REFERENCES Services (id) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE INDEX idx_service_tags_service_id ON ServiceTags (service_id); +CREATE INDEX idx_service_tags_tag_id ON ServiceTags (tag_id); + +CREATE TABLE IF NOT EXISTS ConfigMaps ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + immutable BOOLEAN DEFAULT false, + data TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + use_vault BOOLEAN DEFAULT true +); + +CREATE INDEX idx_config_maps_name ON ConfigMaps (name); + +CREATE TABLE IF NOT EXISTS VolumeMounts ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + name VARCHAR(255) NOT NULL, + config_map_name VARCHAR(255), + secret_name VARCHAR(255), + version INTEGER DEFAULT 1, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (config_map_name) REFERENCES ConfigMaps (name) ON DELETE CASCADE, + FOREIGN KEY (secret_name) REFERENCES Secrets (name) ON DELETE CASCADE +); + +CREATE INDEX idx_volume_mounts_uuid ON VolumeMounts (uuid); +CREATE INDEX idx_volume_mounts_config_map_name ON VolumeMounts (config_map_name); +CREATE INDEX idx_volume_mounts_secret_name ON VolumeMounts (secret_name); + +CREATE TABLE IF NOT EXISTS FogVolumeMounts ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + fog_uuid VARCHAR(36), + volume_mount_uuid VARCHAR(36), + FOREIGN KEY (fog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE, + FOREIGN KEY (volume_mount_uuid) REFERENCES VolumeMounts (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_volume_mounts_fog_uuid ON FogVolumeMounts (fog_uuid); +CREATE INDEX idx_fog_volume_mounts_volume_mount_uuid ON FogVolumeMounts (volume_mount_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceExecStatuses ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + status VARCHAR(255) DEFAULT 'INACTIVE', + exec_session_id VARCHAR(255), + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_exec_status_microservice_uuid ON MicroserviceExecStatuses (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceHealthChecks ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + test TEXT, + interval FLOAT, + timeout FLOAT, + start_period FLOAT, + start_interval FLOAT, + retries INT, + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_health_check_microservice_uuid ON MicroserviceHealthChecks (microservice_uuid); + +CREATE TABLE IF NOT EXISTS Events ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + timestamp BIGINT NOT NULL, + event_type VARCHAR(20) NOT NULL, + endpoint_type VARCHAR(10) NOT NULL, + actor_id VARCHAR(255), + method VARCHAR(10), + resource_type VARCHAR(50), + resource_id VARCHAR(255), + endpoint_path TEXT NOT NULL, + ip_address VARCHAR(45), + status VARCHAR(20) NOT NULL, + status_code INTEGER, + status_message TEXT, + request_id VARCHAR(255), + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX IF NOT EXISTS idx_events_timestamp ON Events (timestamp); +CREATE INDEX IF NOT EXISTS idx_events_endpoint_type ON Events (endpoint_type); +CREATE INDEX IF NOT EXISTS idx_events_actor_id ON Events (actor_id); +CREATE INDEX IF NOT EXISTS idx_events_resource_type ON Events (resource_type); +CREATE INDEX IF NOT EXISTS idx_events_status ON Events (status); +CREATE INDEX IF NOT EXISTS idx_events_method ON Events (method); +CREATE INDEX IF NOT EXISTS idx_events_event_type ON Events (event_type); +CREATE INDEX IF NOT EXISTS idx_events_created_at ON Events (created_at); + +CREATE TABLE IF NOT EXISTS MicroserviceLogStatuses ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + microservice_uuid VARCHAR(36), + log_session_id TEXT, + session_id TEXT UNIQUE NOT NULL, + status TEXT, + tail_config TEXT, + agent_connected BOOLEAN DEFAULT false, + user_connected BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_log_status_microservice_uuid ON MicroserviceLogStatuses (microservice_uuid); +CREATE INDEX idx_microservice_log_status_session_id ON MicroserviceLogStatuses (session_id); + +CREATE TABLE IF NOT EXISTS FogLogStatuses ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + iofog_uuid VARCHAR(36), + log_session_id TEXT, + session_id TEXT UNIQUE NOT NULL, + status TEXT, + tail_config TEXT, + agent_connected BOOLEAN DEFAULT false, + user_connected BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_log_status_iofog_uuid ON FogLogStatuses (iofog_uuid); +CREATE INDEX idx_fog_log_status_session_id ON FogLogStatuses (session_id); + +CREATE TABLE IF NOT EXISTS RbacRoles ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + kind TEXT DEFAULT 'Role', + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS RbacRoleRules ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + role_id INTEGER NOT NULL, + api_groups TEXT NOT NULL, + resources TEXT NOT NULL, + verbs TEXT NOT NULL, + resource_names TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (role_id) REFERENCES RbacRoles (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS RbacRoleBindings ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + kind TEXT DEFAULT 'RoleBinding', + role_ref TEXT NOT NULL, + subjects TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + role_id INTEGER +); + +CREATE TABLE IF NOT EXISTS RbacServiceAccounts ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT NOT NULL, + role_ref TEXT, + role_id INTEGER, + microservice_uuid VARCHAR(36), + application_id INTEGER, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_rbac_role_rules_role_id ON RbacRoleRules (role_id); +CREATE INDEX idx_rbac_roles_name ON RbacRoles (name); +CREATE INDEX idx_rbac_role_bindings_name ON RbacRoleBindings (name); +CREATE INDEX idx_rbac_service_accounts_name ON RbacServiceAccounts (name); + +CREATE TABLE IF NOT EXISTS RbacCacheVersion ( + id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1), + version INTEGER NOT NULL DEFAULT 1, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_rbac_role_bindings_role_id ON RbacRoleBindings (role_id); + +CREATE INDEX idx_rbac_service_accounts_role_id ON RbacServiceAccounts (role_id); +CREATE UNIQUE INDEX idx_rbac_service_accounts_microservice_uuid_unique ON RbacServiceAccounts (microservice_uuid) WHERE microservice_uuid IS NOT NULL; +CREATE UNIQUE INDEX idx_rbac_service_accounts_application_id_name_unique ON RbacServiceAccounts (application_id, name); + +CREATE TABLE IF NOT EXISTS ClusterControllers ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + host VARCHAR(255), + process_id INTEGER, + last_heartbeat DATETIME, + is_active BOOLEAN DEFAULT true, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_cluster_controllers_uuid ON ClusterControllers (uuid); +CREATE INDEX idx_cluster_controllers_host ON ClusterControllers (host); +CREATE INDEX idx_cluster_controllers_active ON ClusterControllers (is_active, last_heartbeat); + +CREATE TABLE IF NOT EXISTS NatsOperators ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + seed_secret_name TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS NatsAccounts ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + seed_secret_name TEXT NOT NULL, + is_system BOOLEAN DEFAULT false, + is_leaf_system BOOLEAN DEFAULT false, + operator_id INTEGER NOT NULL, + application_id INTEGER, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (operator_id) REFERENCES NatsOperators (id) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES Applications (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS NatsUsers ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + creds_secret_name TEXT NOT NULL, + is_bearer BOOLEAN DEFAULT false, + account_id INTEGER NOT NULL, + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + nats_user_rule_id INTEGER, + FOREIGN KEY (account_id) REFERENCES NatsAccounts (id) ON DELETE CASCADE, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS NatsInstances ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + iofog_uuid VARCHAR(36), + is_leaf BOOLEAN DEFAULT true, + is_hub BOOLEAN DEFAULT false, + host TEXT, + server_port INTEGER, + leaf_port INTEGER, + cluster_port INTEGER, + mqtt_port INTEGER, + http_port INTEGER, + configmap_name TEXT, + jwt_dir_mount_name TEXT, + cert_secret_name TEXT, + js_storage_size TEXT, + js_memory_store_size TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS NatsConnections ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + source_nats INTEGER NOT NULL, + dest_nats INTEGER NOT NULL, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (source_nats) REFERENCES NatsInstances (id) ON DELETE CASCADE, + FOREIGN KEY (dest_nats) REFERENCES NatsInstances (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS NatsReconcileTasks ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + reason TEXT NOT NULL, + application_id INTEGER, + account_rule_id INTEGER, + user_rule_id INTEGER, + fog_uuids TEXT, + status TEXT NOT NULL DEFAULT 'pending', + leader_uuid VARCHAR(36), + claimed_at DATETIME, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS NatsAccountRules ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + description TEXT, + info_url TEXT, + max_connections INTEGER, + max_leaf_node_connections INTEGER, + max_data BIGINT, + max_exports INTEGER, + max_imports INTEGER, + max_msg_payload INTEGER, + max_subscriptions INTEGER, + exports_allow_wildcards BOOLEAN DEFAULT true, + disallow_bearer BOOLEAN, + response_permissions TEXT, + resp_max INTEGER, + resp_ttl BIGINT, + imports TEXT, + exports TEXT, + mem_storage BIGINT, + disk_storage BIGINT, + streams INTEGER, + consumer INTEGER, + max_ack_pending INTEGER, + mem_max_stream_bytes BIGINT, + disk_max_stream_bytes BIGINT, + max_bytes_required BOOLEAN, + tiered_limits TEXT, + pub_allow TEXT, + pub_deny TEXT, + sub_allow TEXT, + sub_deny TEXT, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS NatsUserRules ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + description TEXT, + max_subscriptions INTEGER, + max_payload INTEGER, + max_data BIGINT, + bearer_token BOOLEAN DEFAULT false, + proxy_required BOOLEAN, + allowed_connection_types TEXT, + src TEXT, + times TEXT, + times_location TEXT, + resp_max INTEGER, + resp_ttl BIGINT, + pub_allow TEXT, + pub_deny TEXT, + sub_allow TEXT, + sub_deny TEXT, + tags TEXT, + created_at DATETIME, + updated_at DATETIME +); + +CREATE UNIQUE INDEX idx_nats_accounts_application_id_unique ON NatsAccounts (application_id) WHERE application_id IS NOT NULL; +CREATE INDEX idx_nats_accounts_application_id ON NatsAccounts (application_id); +CREATE UNIQUE INDEX idx_nats_users_account_id_name ON NatsUsers (account_id, name); +CREATE INDEX idx_nats_users_account_id ON NatsUsers (account_id); +CREATE INDEX idx_nats_users_microservice_uuid ON NatsUsers (microservice_uuid); +CREATE INDEX idx_nats_users_nats_user_rule_id ON NatsUsers (nats_user_rule_id); +CREATE UNIQUE INDEX idx_nats_instances_iofog_uuid_unique ON NatsInstances (iofog_uuid); +CREATE INDEX idx_nats_instances_iofog_uuid ON NatsInstances (iofog_uuid); +CREATE UNIQUE INDEX idx_nats_connections_source_dest_unique ON NatsConnections (source_nats, dest_nats); +CREATE INDEX idx_nats_connections_source_nats ON NatsConnections (source_nats); +CREATE INDEX idx_nats_connections_dest_nats ON NatsConnections (dest_nats); +CREATE INDEX idx_nats_account_rules_name ON NatsAccountRules (name); +CREATE INDEX idx_nats_user_rules_name ON NatsUserRules (name); +CREATE INDEX idx_nats_reconcile_tasks_status_claimed ON NatsReconcileTasks (status, claimed_at); + +CREATE INDEX idx_applications_nats_rule_id ON Applications (nats_rule_id); +CREATE INDEX idx_microservices_nats_rule_id ON Microservices (nats_rule_id); +CREATE INDEX idx_microservices_nats_account_id ON Microservices (nats_account_id); +CREATE INDEX idx_microservices_nats_user_id ON Microservices (nats_user_id); From 9f5cea8545baf2009abb0358c89473130467070e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 03:16:57 +0300 Subject: [PATCH 03/75] Add v3.8.0 database seeders for Architectures and catalog images Seeds five architecture rows (auto through arm), docker.io registry, and per-architecture catalog item images. --- .../seeders/mysql/db_seeder_mysql_v3.8.0.sql | 51 +++++++++++++++++++ .../seeders/postgres/db_seeder_pg_v3.8.0.sql | 51 +++++++++++++++++++ .../sqlite/db_seeder_sqlite_v3.8.0.sql | 47 +++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql create mode 100644 src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql create mode 100644 src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql diff --git a/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql b/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql new file mode 100644 index 00000000..5ff5df66 --- /dev/null +++ b/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql @@ -0,0 +1,51 @@ +START TRANSACTION; + +INSERT INTO `Registries` (url, is_public, user_name, password, user_email) +VALUES + ('docker.io', true, '', '', ''), + ('from_cache', true, '', '', ''); + +INSERT INTO `CatalogItems` (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) +VALUES + ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), + ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), + ('Debug', 'The built-in debugger for Datasance PoT Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + +INSERT INTO `Architectures` (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) +VALUES + (0, 'auto', 'iointegrator0.png', 'Architecture will be selected on provision', 1, 3, 2), + (1, 'amd64', 'iointegrator1.png', 'Standard x86_64 Linux. Compatible with common Linux distributions such as Ubuntu, Red Hat, and CentOS.', 1, 3, 2), + (2, 'arm64', 'iointegrator2.png', '64-bit ARM Linux (aarch64). Microservices for this architecture are built for ARM64 systems.', 1, 3, 2), + (3, 'riscv64', 'iointegrator3.png', 'RISC-V 64-bit Linux. Microservices for this architecture are built for RISC-V systems.', 1, 3, 2), + (4, 'arm', 'iointegrator4.png', '32-bit ARM Linux. Microservices for this architecture are built for 32-bit ARM systems.', 1, 3, 2); + +UPDATE `Fogs` +SET arch_id = 0 +WHERE arch_id IS NULL; + +INSERT INTO `CatalogItemImages` (catalog_item_id, arch_id, container_image) +VALUES + (1, 1, 'ghcr.io/datasance/router:latest'), + (1, 2, 'ghcr.io/datasance/router:latest'), + (1, 3, 'ghcr.io/datasance/router:latest'), + (1, 4, 'ghcr.io/datasance/router:latest'), + (2, 1, 'ghcr.io/datasance/restblue:latest'), + (2, 2, 'ghcr.io/datasance/restblue:latest'), + (2, 3, 'ghcr.io/datasance/restblue:latest'), + (2, 4, 'ghcr.io/datasance/restblue:latest'), + (3, 1, 'ghcr.io/datasance/hal:latest'), + (3, 2, 'ghcr.io/datasance/hal:latest'), + (3, 3, 'ghcr.io/datasance/hal:latest'), + (3, 4, 'ghcr.io/datasance/hal:latest'), + (4, 1, 'ghcr.io/datasance/node-debugger:latest'), + (4, 2, 'ghcr.io/datasance/node-debugger:latest'), + (4, 3, 'ghcr.io/datasance/node-debugger:latest'), + (4, 4, 'ghcr.io/datasance/node-debugger:latest'), + (5, 1, 'ghcr.io/datasance/nats:latest'), + (5, 2, 'ghcr.io/datasance/nats:latest'), + (5, 3, 'ghcr.io/datasance/nats:latest'), + (5, 4, 'ghcr.io/datasance/nats:latest'); + +COMMIT; diff --git a/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql b/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql new file mode 100644 index 00000000..eb6ec5af --- /dev/null +++ b/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql @@ -0,0 +1,51 @@ +START TRANSACTION; + +INSERT INTO "Registries" (url, is_public, user_name, password, user_email) +VALUES + ('docker.io', true, '', '', ''), + ('from_cache', true, '', '', ''); + +INSERT INTO "CatalogItems" (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) +VALUES + ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), + ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), + ('Debug', 'The built-in debugger for Datasance PoT Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + +INSERT INTO "Architectures" (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) +VALUES + (0, 'auto', 'iointegrator0.png', 'Architecture will be selected on provision', 1, 3, 2), + (1, 'amd64', 'iointegrator1.png', 'Standard x86_64 Linux. Compatible with common Linux distributions such as Ubuntu, Red Hat, and CentOS.', 1, 3, 2), + (2, 'arm64', 'iointegrator2.png', '64-bit ARM Linux (aarch64). Microservices for this architecture are built for ARM64 systems.', 1, 3, 2), + (3, 'riscv64', 'iointegrator3.png', 'RISC-V 64-bit Linux. Microservices for this architecture are built for RISC-V systems.', 1, 3, 2), + (4, 'arm', 'iointegrator4.png', '32-bit ARM Linux. Microservices for this architecture are built for 32-bit ARM systems.', 1, 3, 2); + +UPDATE "Fogs" +SET arch_id = 0 +WHERE arch_id IS NULL; + +INSERT INTO "CatalogItemImages" (catalog_item_id, arch_id, container_image) +VALUES + (1, 1, 'ghcr.io/datasance/router:latest'), + (1, 2, 'ghcr.io/datasance/router:latest'), + (1, 3, 'ghcr.io/datasance/router:latest'), + (1, 4, 'ghcr.io/datasance/router:latest'), + (2, 1, 'ghcr.io/datasance/restblue:latest'), + (2, 2, 'ghcr.io/datasance/restblue:latest'), + (2, 3, 'ghcr.io/datasance/restblue:latest'), + (2, 4, 'ghcr.io/datasance/restblue:latest'), + (3, 1, 'ghcr.io/datasance/hal:latest'), + (3, 2, 'ghcr.io/datasance/hal:latest'), + (3, 3, 'ghcr.io/datasance/hal:latest'), + (3, 4, 'ghcr.io/datasance/hal:latest'), + (4, 1, 'ghcr.io/datasance/node-debugger:latest'), + (4, 2, 'ghcr.io/datasance/node-debugger:latest'), + (4, 3, 'ghcr.io/datasance/node-debugger:latest'), + (4, 4, 'ghcr.io/datasance/node-debugger:latest'), + (5, 1, 'ghcr.io/datasance/nats:latest'), + (5, 2, 'ghcr.io/datasance/nats:latest'), + (5, 3, 'ghcr.io/datasance/nats:latest'), + (5, 4, 'ghcr.io/datasance/nats:latest'); + +COMMIT; diff --git a/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql b/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql new file mode 100644 index 00000000..3cb80c52 --- /dev/null +++ b/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql @@ -0,0 +1,47 @@ +INSERT INTO `Registries` (url, is_public, user_name, password, user_email) +VALUES + ('docker.io', true, '', '', ''), + ('from_cache', true, '', '', ''); + +INSERT INTO `CatalogItems` (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) +VALUES + ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), + ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), + ('Debug', 'The built-in debugger for Datasance PoT Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + +INSERT INTO `Architectures` (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) +VALUES + (0, 'auto', 'iointegrator0.png', 'Architecture will be selected on provision', 1, 3, 2), + (1, 'amd64', 'iointegrator1.png', 'Standard x86_64 Linux. Compatible with common Linux distributions such as Ubuntu, Red Hat, and CentOS.', 1, 3, 2), + (2, 'arm64', 'iointegrator2.png', '64-bit ARM Linux (aarch64). Microservices for this architecture are built for ARM64 systems.', 1, 3, 2), + (3, 'riscv64', 'iointegrator3.png', 'RISC-V 64-bit Linux. Microservices for this architecture are built for RISC-V systems.', 1, 3, 2), + (4, 'arm', 'iointegrator4.png', '32-bit ARM Linux. Microservices for this architecture are built for 32-bit ARM systems.', 1, 3, 2); + +UPDATE `Fogs` +SET arch_id = 0 +WHERE arch_id IS NULL; + +INSERT INTO `CatalogItemImages` (catalog_item_id, arch_id, container_image) +VALUES + (1, 1, 'ghcr.io/datasance/router:latest'), + (1, 2, 'ghcr.io/datasance/router:latest'), + (1, 3, 'ghcr.io/datasance/router:latest'), + (1, 4, 'ghcr.io/datasance/router:latest'), + (2, 1, 'ghcr.io/datasance/restblue:latest'), + (2, 2, 'ghcr.io/datasance/restblue:latest'), + (2, 3, 'ghcr.io/datasance/restblue:latest'), + (2, 4, 'ghcr.io/datasance/restblue:latest'), + (3, 1, 'ghcr.io/datasance/hal:latest'), + (3, 2, 'ghcr.io/datasance/hal:latest'), + (3, 3, 'ghcr.io/datasance/hal:latest'), + (3, 4, 'ghcr.io/datasance/hal:latest'), + (4, 1, 'ghcr.io/datasance/node-debugger:latest'), + (4, 2, 'ghcr.io/datasance/node-debugger:latest'), + (4, 3, 'ghcr.io/datasance/node-debugger:latest'), + (4, 4, 'ghcr.io/datasance/node-debugger:latest'), + (5, 1, 'ghcr.io/datasance/nats:latest'), + (5, 2, 'ghcr.io/datasance/nats:latest'), + (5, 3, 'ghcr.io/datasance/nats:latest'), + (5, 4, 'ghcr.io/datasance/nats:latest'); From 58ae96a802dda2d7dba32c325837bdc252afe201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 03:17:29 +0300 Subject: [PATCH 04/75] Rename FogType to Architecture across models and managers Maps fog_type_id to arch_id, Applications table name, adds availableRuntimes and isController, and removes deprecated message speed fields from the Fog model. --- ...ype-manager.js => architecture-manager.js} | 8 +++---- src/data/managers/catalog-item-manager.js | 4 ++-- src/data/managers/microservice-manager.js | 12 +++++----- src/data/models/application.js | 2 +- .../models/{fogtype.js => architecture.js} | 14 +++++------ src/data/models/catalogitemimage.js | 8 +++---- src/data/models/fog.js | 23 ++++++------------- src/data/models/index.js | 18 +++++++-------- src/data/models/microservice.js | 5 ++++ src/services/controller-service.js | 4 ++-- 10 files changed, 47 insertions(+), 51 deletions(-) rename src/data/managers/{iofog-type-manager.js => architecture-manager.js} (79%) rename src/data/models/{fogtype.js => architecture.js} (74%) diff --git a/src/data/managers/iofog-type-manager.js b/src/data/managers/architecture-manager.js similarity index 79% rename from src/data/managers/iofog-type-manager.js rename to src/data/managers/architecture-manager.js index 1111bf6b..7611d1f0 100644 --- a/src/data/managers/iofog-type-manager.js +++ b/src/data/managers/architecture-manager.js @@ -13,13 +13,13 @@ const BaseManager = require('./base-manager') const models = require('../models') -const FogType = models.FogType +const Architecture = models.Architecture -class FogTypeManager extends BaseManager { +class ArchitectureManager extends BaseManager { getEntity () { - return FogType + return Architecture } } -const instance = new FogTypeManager() +const instance = new ArchitectureManager() module.exports = instance diff --git a/src/data/managers/catalog-item-manager.js b/src/data/managers/catalog-item-manager.js index c190fffe..5f2ab3f5 100644 --- a/src/data/managers/catalog-item-manager.js +++ b/src/data/managers/catalog-item-manager.js @@ -30,7 +30,7 @@ class CatalogItemManager extends BaseManager { model: CatalogItemImage, as: 'images', required: false, - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }, { model: CatalogItemInputType, @@ -56,7 +56,7 @@ class CatalogItemManager extends BaseManager { model: CatalogItemImage, as: 'images', required: false, - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }, { model: CatalogItemInputType, diff --git a/src/data/managers/microservice-manager.js b/src/data/managers/microservice-manager.js index c39d1a44..80c0f5d6 100644 --- a/src/data/managers/microservice-manager.js +++ b/src/data/managers/microservice-manager.js @@ -111,7 +111,7 @@ class MicroserviceManager extends BaseManager { model: CatalogItemImage, as: 'images', required: false, - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }, { model: Registry, @@ -126,7 +126,7 @@ class MicroserviceManager extends BaseManager { include: [{ model: CatalogItemImage, as: 'images', - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }], attributes: ['picture', 'registryId'] }, @@ -207,7 +207,7 @@ class MicroserviceManager extends BaseManager { model: CatalogItemImage, as: 'images', required: false, - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }, { model: Registry, @@ -224,7 +224,7 @@ class MicroserviceManager extends BaseManager { model: CatalogItemImage, as: 'images', required: true, - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }, { model: Registry, @@ -333,7 +333,7 @@ class MicroserviceManager extends BaseManager { model: CatalogItemImage, as: 'images', required: false, - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }, { model: Registry, @@ -348,7 +348,7 @@ class MicroserviceManager extends BaseManager { include: [{ model: CatalogItemImage, as: 'images', - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }], attributes: ['picture', 'registryId', 'category'] }, diff --git a/src/data/models/application.js b/src/data/models/application.js index 51687bc9..f4d9bbde 100644 --- a/src/data/models/application.js +++ b/src/data/models/application.js @@ -41,7 +41,7 @@ module.exports = (sequelize, DataTypes) => { allowNull: true } }, { - tableName: 'Flows', + tableName: 'Applications', timestamps: true, underscored: true }) diff --git a/src/data/models/fogtype.js b/src/data/models/architecture.js similarity index 74% rename from src/data/models/fogtype.js rename to src/data/models/architecture.js index ff3c1037..8ec11768 100644 --- a/src/data/models/fogtype.js +++ b/src/data/models/architecture.js @@ -1,6 +1,6 @@ 'use strict' module.exports = (sequelize, DataTypes) => { - const FogType = sequelize.define('FogType', { + const Architecture = sequelize.define('Architecture', { id: { type: DataTypes.INTEGER, primaryKey: true, @@ -21,12 +21,12 @@ module.exports = (sequelize, DataTypes) => { field: 'description' } }, { - tableName: 'FogTypes', + tableName: 'Architectures', timestamps: false, underscored: true }) - FogType.associate = function (models) { - FogType.belongsTo(models.CatalogItem, { + Architecture.associate = function (models) { + Architecture.belongsTo(models.CatalogItem, { foreignKey: { name: 'networkCatalogItemId', field: 'network_catalog_item_id' @@ -34,7 +34,7 @@ module.exports = (sequelize, DataTypes) => { as: 'networkCatalogItem' }) - FogType.belongsTo(models.CatalogItem, { + Architecture.belongsTo(models.CatalogItem, { foreignKey: { name: 'halCatalogItemId', field: 'hal_catalog_item_id' @@ -42,7 +42,7 @@ module.exports = (sequelize, DataTypes) => { as: 'halCatalogItem' }) - FogType.belongsTo(models.CatalogItem, { + Architecture.belongsTo(models.CatalogItem, { foreignKey: { name: 'bluetoothCatalogItemId', field: 'bluetooth_catalog_item_id' @@ -50,5 +50,5 @@ module.exports = (sequelize, DataTypes) => { as: 'bluetoothCatalogItem' }) } - return FogType + return Architecture } diff --git a/src/data/models/catalogitemimage.js b/src/data/models/catalogitemimage.js index 37732b36..5f741de3 100644 --- a/src/data/models/catalogitemimage.js +++ b/src/data/models/catalogitemimage.js @@ -36,12 +36,12 @@ module.exports = (sequelize, DataTypes) => { onDelete: 'cascade' }) - CatalogItemImage.belongsTo(models.FogType, { + CatalogItemImage.belongsTo(models.Architecture, { foreignKey: { - name: 'fogTypeId', - field: 'fog_type_id' + name: 'archId', + field: 'arch_id' }, - as: 'fogType', + as: 'architecture', onDelete: 'cascade' }) } diff --git a/src/data/models/fog.js b/src/data/models/fog.js index bd0342ca..b16b03cb 100644 --- a/src/data/models/fog.js +++ b/src/data/models/fog.js @@ -170,22 +170,13 @@ module.exports = (sequelize, DataTypes) => { host: { type: DataTypes.TEXT }, - processedMessages: { - type: DataTypes.BIGINT, - get () { - return convertToInt(this.getDataValue('processedMessages')) - }, - defaultValue: 0, - field: 'processed_messages' - }, catalogItemMessageCounts: { type: DataTypes.TEXT, field: 'catalog_item_message_counts' }, - messageSpeed: { - type: DataTypes.FLOAT, - defaultValue: 0.000, - field: 'message_speed' + availableRuntimes: { + type: DataTypes.TEXT, + field: 'available_runtimes' }, lastCommandTime: { type: DataTypes.BIGINT, @@ -367,12 +358,12 @@ module.exports = (sequelize, DataTypes) => { underscored: true }) Fog.associate = function (models) { - Fog.belongsTo(models.FogType, { + Fog.belongsTo(models.Architecture, { foreignKey: { - name: 'fogTypeId', - field: 'fog_type_id' + name: 'archId', + field: 'arch_id' }, - as: 'fogType', + as: 'architecture', defaultValue: 0 }) diff --git a/src/data/models/index.js b/src/data/models/index.js index 1cac68d3..a6f323fa 100644 --- a/src/data/models/index.js +++ b/src/data/models/index.js @@ -34,7 +34,7 @@ const initializeModels = (sequelize) => { db.Sequelize = Sequelize } -const configureImage = async (db, name, fogTypes, images) => { +const configureImage = async (db, name, architectures, images) => { const isNats = name === constants.NATS_CATALOG_NAME const catalogItem = await db.CatalogItem.findOne({ where: isNats ? { name } : { name, isPublic: false } @@ -43,13 +43,13 @@ const configureImage = async (db, name, fogTypes, images) => { logger.warn(`Catalog item not found for ${name}, skipping image configuration`) return } - for (const fogType of fogTypes) { - if (fogType.id === 0) { + for (const architecture of architectures) { + if (architecture.id === 0) { // Skip auto detect type continue } - const image = lget(images, fogType.id, '') - await db.CatalogItemImage.update({ containerImage: image }, { where: { fogTypeId: fogType.id, catalogItemId: catalogItem.id } }) + const image = lget(images, architecture.id, '') + await db.CatalogItemImage.update({ containerImage: image }, { where: { archId: architecture.id, catalogItemId: catalogItem.id } }) } } @@ -86,10 +86,10 @@ db.initDB = async (isStart) => { } // Configure system images - const fogTypes = await db.FogType.findAll({}) - await configureImage(db, constants.ROUTER_CATALOG_NAME, fogTypes, config.get('systemImages.router', {})) - await configureImage(db, constants.DEBUG_CATALOG_NAME, fogTypes, config.get('systemImages.debug', {})) - await configureImage(db, constants.NATS_CATALOG_NAME, fogTypes, config.get('systemImages.nats', {})) + const architectures = await db.Architecture.findAll({}) + await configureImage(db, constants.ROUTER_CATALOG_NAME, architectures, config.get('systemImages.router', {})) + await configureImage(db, constants.DEBUG_CATALOG_NAME, architectures, config.get('systemImages.debug', {})) + await configureImage(db, constants.NATS_CATALOG_NAME, architectures, config.get('systemImages.nats', {})) // Initialize controller UUID try { diff --git a/src/data/models/microservice.js b/src/data/models/microservice.js index be04c777..39f4b156 100644 --- a/src/data/models/microservice.js +++ b/src/data/models/microservice.js @@ -143,6 +143,11 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.INTEGER, field: 'nats_rule_id', allowNull: true + }, + isController: { + type: DataTypes.BOOLEAN, + field: 'is_controller', + defaultValue: false } }, { tableName: 'Microservices', diff --git a/src/services/controller-service.js b/src/services/controller-service.js index 79fa63ae..e0513035 100644 --- a/src/services/controller-service.js +++ b/src/services/controller-service.js @@ -11,13 +11,13 @@ * */ -const ioFogTypesManager = require('../data/managers/iofog-type-manager') +const architectureManager = require('../data/managers/architecture-manager') const TransactionDecorator = require('../decorators/transaction-decorator') const packageJson = require('../../package') const AppHelper = require('../helpers/app-helper') const getFogTypes = async function (isCLI, transaction) { - const ioFogTypes = await ioFogTypesManager.findAll({}, transaction) + const ioFogTypes = await architectureManager.findAll({}, transaction) const response = [] for (const ioFogType of ioFogTypes) { From 86a13ea66d94b71201e174764d2b6d77d83ba255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 03:17:37 +0300 Subject: [PATCH 05/75] Point fresh install migration runner at v3.8.0 SQL files Updates sqlite, mysql, and postgres migration and seeder paths and schema version tracking from 1.1.0/1.0.2 to 3.8.0. --- src/data/providers/database-provider.js | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/data/providers/database-provider.js b/src/data/providers/database-provider.js index 2836f2fc..3ae7d6bb 100644 --- a/src/data/providers/database-provider.js +++ b/src/data/providers/database-provider.js @@ -249,10 +249,10 @@ class DatabaseProvider { } } - // SQLite migration + // SQLite migration — greenfield v3.8.0 (see src/data/migrations/README.md) async runMigrationSQLite (dbName) { - const migrationSqlPath = path.resolve(__dirname, '../migrations/sqlite/db_migration_sqlite_v1.1.0.sql') - const migrationVersion = '1.1.0' + const migrationSqlPath = path.resolve(__dirname, '../migrations/sqlite/db_migration_sqlite_v3.8.0.sql') + const migrationVersion = '3.8.0' if (!fs.existsSync(migrationSqlPath)) { logger.error(`Migration file not found: ${migrationSqlPath}`) @@ -322,10 +322,10 @@ class DatabaseProvider { } } - // MySQL migration + // MySQL migration — greenfield v3.8.0 (see src/data/migrations/README.md) async runMigrationMySQL (db) { - const migrationSqlPath = path.resolve(__dirname, '../migrations/mysql/db_migration_mysql_v1.1.0.sql') - const migrationVersion = '1.1.0' + const migrationSqlPath = path.resolve(__dirname, '../migrations/mysql/db_migration_mysql_v3.8.0.sql') + const migrationVersion = '3.8.0' if (!fs.existsSync(migrationSqlPath)) { logger.error(`Migration file not found: ${migrationSqlPath}`) @@ -383,10 +383,10 @@ class DatabaseProvider { } } - // PostgreSQL migration + // PostgreSQL migration — greenfield v3.8.0 (see src/data/migrations/README.md) async runMigrationPostgres (db) { - const migrationSqlPath = path.resolve(__dirname, '../migrations/postgres/db_migration_pg_v1.1.0.sql') - const migrationVersion = '1.1.0' + const migrationSqlPath = path.resolve(__dirname, '../migrations/postgres/db_migration_pg_v3.8.0.sql') + const migrationVersion = '3.8.0' if (!fs.existsSync(migrationSqlPath)) { logger.error(`Migration file not found: ${migrationSqlPath}`) @@ -458,10 +458,10 @@ class DatabaseProvider { } } - // SQLite seeder + // SQLite seeder — greenfield v3.8.0 (see src/data/migrations/README.md) async runSeederSQLite (dbName) { - const seederSqlPath = path.resolve(__dirname, '../seeders/sqlite/db_seeder_sqlite_v1.0.2.sql') - const seederVersion = '1.0.2' + const seederSqlPath = path.resolve(__dirname, '../seeders/sqlite/db_seeder_sqlite_v3.8.0.sql') + const seederVersion = '3.8.0' if (!fs.existsSync(seederSqlPath)) { logger.error(`Seeder file not found: ${seederSqlPath}`) @@ -530,10 +530,10 @@ class DatabaseProvider { } } - // MySQL seeder + // MySQL seeder — greenfield v3.8.0 (see src/data/migrations/README.md) async runSeederMySQL (db) { - const seederSqlPath = path.resolve(__dirname, '../seeders/mysql/db_seeder_mysql_v1.0.2.sql') - const seederVersion = '1.0.2' + const seederSqlPath = path.resolve(__dirname, '../seeders/mysql/db_seeder_mysql_v3.8.0.sql') + const seederVersion = '3.8.0' if (!fs.existsSync(seederSqlPath)) { logger.error(`Seeder file not found: ${seederSqlPath}`) @@ -580,10 +580,10 @@ class DatabaseProvider { } } - // PostgreSQL seeder + // PostgreSQL seeder — greenfield v3.8.0 (see src/data/migrations/README.md) async runSeederPostgres (db) { - const seederSqlPath = path.resolve(__dirname, '../seeders/postgres/db_seeder_pg_v1.0.2.sql') - const seederVersion = '1.0.2' + const seederSqlPath = path.resolve(__dirname, '../seeders/postgres/db_seeder_pg_v3.8.0.sql') + const seederVersion = '3.8.0' if (!fs.existsSync(seederSqlPath)) { logger.error(`Seeder file not found: ${seederSqlPath}`) From 77aa00582432ecc253be016bbe46e49c6c3f2ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 03:18:04 +0300 Subject: [PATCH 06/75] Update unit tests for Architecture rename and current agent provisioning Fixes Mocha 10 bin resolution, stubs architecture-manager and FogKeyService, and removes tests for services that no longer exist. --- scripts/test.js | 3 +- .../rvaluesVarSubstitionMiddleware.test.js | 185 ++-- .../src/services/access-token-service.test.js | 82 -- test/src/services/agent-service.test.js | 53 +- test/src/services/controller-service.test.js | 12 +- .../email-activation-code-service.test.js | 177 ---- .../services/microservices-service.test.js | 8 +- test/src/services/user-service.test.js | 814 ------------------ 8 files changed, 115 insertions(+), 1219 deletions(-) delete mode 100644 test/src/services/access-token-service.test.js delete mode 100644 test/src/services/email-activation-code-service.test.js delete mode 100644 test/src/services/user-service.test.js diff --git a/scripts/test.js b/scripts/test.js index 3236bbd3..c394b30d 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -12,7 +12,6 @@ */ const execSync = require('child_process').execSync -const path = require('path') const { setDbEnvVars } = require('./util') @@ -28,7 +27,7 @@ function test (useReporter, extraArgs) { options.env = setDbEnvVars(options.env) - const mochaBin = path.join(__dirname, '..', 'node_modules', 'mocha', 'bin', 'mocha') + const mochaBin = require.resolve('mocha/bin/mocha.js') const mochaReporterOptions = '--reporter mocha-junit-reporter --reporter-options mochaFile=./unit-results.xml' let mochaCmd = useReporter ? [mochaBin, mochaReporterOptions].join(' ') : mochaBin if (extraArgs && extraArgs.length) { diff --git a/test/src/middlewares/rvaluesVarSubstitionMiddleware.test.js b/test/src/middlewares/rvaluesVarSubstitionMiddleware.test.js index 5a0810f8..de6e6d8f 100755 --- a/test/src/middlewares/rvaluesVarSubstitionMiddleware.test.js +++ b/test/src/middlewares/rvaluesVarSubstitionMiddleware.test.js @@ -2,7 +2,6 @@ const { expect } = require('chai') const sinon = require('sinon') const { substitutionMiddleware } = require('../../../src/helpers/template-helper') -const UserManager = require('../../../src/data/managers/user-manager') const MicroservicesService = require('../../../src/services/microservices-service') const ApplicationManager = require('../../../src/data/managers/application-manager') const FogService = require('../../../src/services/iofog-service') @@ -15,11 +14,7 @@ describe('rvaluesVarSubstitionMiddleware', () => { afterEach(() => $sandbox.restore()) context('GET request method not calling microservices and fog list services', () => { - def('user', () => 'user!') - def('name', () => 'testName') - def('description', () => 'testDescription') - def('isActivated', () => true) def('req', () => ({ method: 'GET', @@ -31,11 +26,10 @@ describe('rvaluesVarSubstitionMiddleware', () => { def('responseApp', () => Promise.resolve()) def('responseFogList', () => Promise.resolve()) def('response', () => Promise.resolve()) - def('nextfct', () => sinon.spy() ) - def('subject', () => $subject($req, $response, $nextfct )) + def('nextfct', () => sinon.spy()) + def('subject', () => $subject($req, $response, $nextfct)) beforeEach(() => { - $sandbox.stub(UserManager, 'checkAuthentication').resolves({ user: $user}) $sandbox.stub(MicroservicesService, 'listMicroservicesEndPoint').resolves($responseApp) $sandbox.stub(FogService, 'getFogListEndPoint').resolves($responseFogList) }) @@ -50,21 +44,13 @@ describe('rvaluesVarSubstitionMiddleware', () => { context('when variable interpolation/expansion needed', () => { def('req', () => ({ method: 'POST', - headers: { authorization: $token }, body: { name: $name, description: '{{ self.name | upcase }}', }, })) - - def('responseApp', ({ - microservices: [] - })) - def('responseFogList', ({ - fogs: [] - })) - it(`succeeds`, async () => { + it('succeeds', async () => { await $subject expect($req.body.description).to.be.equal($name.toUpperCase()) }) @@ -72,11 +58,7 @@ describe('rvaluesVarSubstitionMiddleware', () => { }) context('POST request method triggering middleware', () => { - def('token', () => 'token!') - def('name', () => 'testName') - def('description', () => 'testDescription') - def('isActivated', () => true) def('body', () => ({ body: { @@ -88,36 +70,32 @@ describe('rvaluesVarSubstitionMiddleware', () => { def('req', () => ({ method: 'POST', query: { application: $name }, - headers: { authorization: $token }, ...$body })) - def('responseApp', ({ + def('responseApp', () => ({ microservices: [] })) - def('responseFog', ({ + def('responseFog', () => ({})) + def('responseEdgeRes', () => ({ + edgeResources: { name: 'testedgeres' } })) - def('responseEdgeRes', ({ - edgeResources: { name: 'testedgeres'} - })) - - def('auth', () => ({user: $token})) + def('response', () => Promise.resolve()) - def('nextfct', () => sinon.spy() ) + def('nextfct', () => sinon.spy()) def('subject', () => $subject($req, $response, $nextfct)) beforeEach(() => { - $sandbox.stub(UserManager, 'checkAuthentication').resolves($auth) $sandbox.stub(ApplicationManager, 'findOnePopulated').resolves($responseApp) $sandbox.stub(MicroservicesService, 'listMicroservicesEndPoint').resolves($responseApp) $sandbox.stub(FogService, 'getFogEndPoint').resolves($responseFog) $sandbox.stub(EdgeResourceService, 'getEdgeResource').resolves($responseEdgeRes) }) - it('calls MicroservicesService.listMicroservicesEndPoint and FogService.getFogListEndPoint with correct args', async () => { + it('calls next after POST body substitution', async () => { await $subject expect($nextfct).to.have.been.called - expect(UserManager.checkAuthentication).to.have.been.called + expect($req.body.description).to.be.equal($name.toUpperCase()) }) context('Variables substitution and filter findMicroserviceAgent', () => { @@ -130,107 +108,96 @@ describe('rvaluesVarSubstitionMiddleware', () => { videoURL: `{% assign redisApp = \"${$redisAppName}\" | findApplication %}{{ redisApp.microservices | where: \"name\", \"objdetecv4\" | first | map: \"env\" | first | where: \"key\" , \"RES_URL\" | first | map: \"value\" | first }}`, }, })) - def('responseApp', ({ + def('responseApp', () => ({ microservices: [ { - "name": "objdetecv4", - "applicationId": 1, - "ports": [ + name: 'objdetecv4', + applicationId: 1, + ports: [ { - "internal": 8080, - "external": 8091, - "publicMode": false + internal: 8080, + external: 8091, + publicMode: false } - ], - "env": [ + ], + env: [ { - "key": "RES_URL", - "value": "http://mycam/img/video.mjpeg" + key: 'RES_URL', + value: 'http://mycam/img/video.mjpeg' } - ] - }, - { - "name": "redis", - "iofogUuid": "TkLh8wzcxb86CRnHQyJkx6VF468JFd4f", - "ports": [ + ] + }, + { + name: 'redis', + iofogUuid: 'TkLh8wzcxb86CRnHQyJkx6VF468JFd4f', + ports: [ { - "internal": 6379, - "external": 6379, - "publicMode": false + internal: 6379, + external: 6379, + publicMode: false } - ], - "application": "main-app", - "flowId": 1 - } - ] + ], + application: 'main-app', + flowId: 1 + } + ] })) - def('responseFog', ({ - "uuid": "TkLh8wzcxb86CRnHQyJkx6VF468JFd4f", - "name": "agent01", - "location": "building01manager", - "host": "myhost01", + def('responseFog', () => ({ + uuid: 'TkLh8wzcxb86CRnHQyJkx6VF468JFd4f', + name: 'agent01', + location: 'building01manager', + host: 'myhost01', })) it('performs variable substitutions and applies filter', async () => { await $subject expect($nextfct).to.have.been.called - expect(UserManager.checkAuthentication).to.have.been.called expect(FogService.getFogEndPoint).to.have.been.called - expect(FogService.getFogEndPoint).to.have.been.calledWith({uuid: "TkLh8wzcxb86CRnHQyJkx6VF468JFd4f"}, $auth, false) - expect(ApplicationManager.findOnePopulated).to.have.been.calledOnce // Verifies the cache logic - expect(ApplicationManager.findOnePopulated).to.have.been.calledWith({name: $redisAppName, userId: $auth.id}, { exclude: ["created_at", "updated_at"] }, {fakeTransaction: true}) + expect(FogService.getFogEndPoint).to.have.been.calledWith({ uuid: 'TkLh8wzcxb86CRnHQyJkx6VF468JFd4f' }, false) + expect(ApplicationManager.findOnePopulated).to.have.been.calledOnce + expect(ApplicationManager.findOnePopulated).to.have.been.calledWith({ exclude: ['created_at', 'updated_at'] }, { fakeTransaction: true }) expect(MicroservicesService.listMicroservicesEndPoint).to.have.been.called - expect(MicroservicesService.listMicroservicesEndPoint).to.have.been.calledWith({applicationName: $redisAppName}, $auth, false) + expect(MicroservicesService.listMicroservicesEndPoint).to.have.been.calledWith({ applicationName: $redisAppName }, false) expect(EdgeResourceService.getEdgeResource).to.not.have.been.called - expect($req.body.serviceredisURL).to.be.equal("myhost01:6379") - expect($req.body.videoURL).to.be.equal("http://mycam/img/video.mjpeg") + expect($req.body.serviceredisURL).to.be.equal('myhost01:6379') + expect($req.body.videoURL).to.be.equal('http://mycam/img/video.mjpeg') }) }) context('Variables substitution and filter edgeresource', () => { - def('responseApp', ({ + def('responseApp', () => ({ microservices: [ { - "name": "objdetecv4", - "applicationId": 1, - "ports": [ + name: 'objdetecv4', + applicationId: 1, + ports: [ { - "internal": 8080, - "external": 8091, - "publicMode": false + internal: 8080, + external: 8091, + publicMode: false } - ], - "env": [ + ], + env: [ { - "key": "RES_URL", - "value": "http://mycam/img/video.mjpeg" + key: 'RES_URL', + value: 'http://mycam/img/video.mjpeg' } - ] - }, - { - "name": "redis", - "iofogUuid": "TkLh8wzcxb86CRnHQyJkx6VF468JFd4f", - "ports": [ + ] + }, + { + name: 'redis', + iofogUuid: 'TkLh8wzcxb86CRnHQyJkx6VF468JFd4f', + ports: [ { - "internal": 6379, - "external": 6379, - "publicMode": false + internal: 6379, + external: 6379, + publicMode: false } - ], - "application": "main-app", - "flowId": 1 - } - ] - })) - def('responseFogList', ({ - fogs: [ - { - "uuid": "TkLh8wzcxb86CRnHQyJkx6VF468JFd4f", - "name": "agent01", - "location": "building01manager", - "host": "myhost01", - } + ], + application: 'main-app', + flowId: 1 + } ] })) @@ -245,10 +212,9 @@ describe('rvaluesVarSubstitionMiddleware', () => { it('performs variable substitutions and applies filter, looking edge resource with version', async () => { await $subject expect($nextfct).to.have.been.called - expect(UserManager.checkAuthentication).to.have.been.called expect(EdgeResourceService.getEdgeResource).to.have.been.called - expect(EdgeResourceService.getEdgeResource).to.have.been.calledWith({ name: "edgeRes", version: "0.1.0" } , $auth) - + expect(EdgeResourceService.getEdgeResource).to.have.been.calledWith({ name: 'edgeRes', version: '0.1.0' }) + expect($req.body.edgeRes).to.be.equal(JSON.stringify($responseEdgeRes)) }) }) @@ -264,10 +230,9 @@ describe('rvaluesVarSubstitionMiddleware', () => { it('performs variable substitutions and applies filter, looking edge resource without version', async () => { await $subject expect($nextfct).to.have.been.called - expect(UserManager.checkAuthentication).to.have.been.called expect(EdgeResourceService.getEdgeResource).to.have.been.called - expect(EdgeResourceService.getEdgeResource).to.have.been.calledWith({ name: "edgeRes", version: undefined } , $auth) - + expect(EdgeResourceService.getEdgeResource).to.have.been.calledWith({ name: 'edgeRes', version: undefined }) + expect($req.body.edgeResWithoutVersion).to.be.equal(JSON.stringify($responseEdgeRes)) }) }) diff --git a/test/src/services/access-token-service.test.js b/test/src/services/access-token-service.test.js deleted file mode 100644 index d75b6361..00000000 --- a/test/src/services/access-token-service.test.js +++ /dev/null @@ -1,82 +0,0 @@ -const { expect } = require('chai') -const sinon = require('sinon') - -const AccessTokenManager = require('../../../src/data/managers/access-token-manager') -const AccessTokenService = require('../../../src/services/access-token-service') - -describe('AccessToken Service', () => { - def('subject', () => AccessTokenService) - def('sandbox', () => sinon.createSandbox()) - - afterEach(() => $sandbox.restore()) - - describe('.createAccessToken()', () => { - const accessToken = 'accessToken' - const transaction = {} - const error = 'Error!' - - def('accessTokenObj', () => 'accessTokenResponse') - - def('subject', () => $subject.createAccessToken(accessToken, transaction)) - def('accessTokenResponse', () => Promise.resolve($accessTokenObj)) - - beforeEach(() => { - $sandbox.stub(AccessTokenManager, 'create').returns($accessTokenResponse) - }) - - it('calls AccessTokenManager#create() with correct args', async () => { - await $subject - expect(AccessTokenManager.create).to.have.been.calledWith(accessToken, transaction) - }) - - context('when AccessTokenManager#create() fails', () => { - def('accessTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AccessTokenManager#create() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal($accessTokenObj) - }) - }) - }) - - describe('.removeAccessTokenByUserId()', () => { - const userId = 15 - const transaction = {} - const error = 'Error!' - - def('removeTokenObj', () => 'removeToken') - - def('subject', () => $subject.removeAccessTokenByUserId(userId, transaction)) - def('removeTokenResponse', () => Promise.resolve($removeTokenObj)) - - beforeEach(() => { - $sandbox.stub(AccessTokenManager, 'delete').returns($removeTokenResponse) - }) - - it('calls AccessTokenManager#delete() with correct args', async () => { - await $subject - expect(AccessTokenManager.delete).to.have.been.calledWith({ - userId: userId, - }, transaction) - }) - - context('when AccessTokenManager#delete() fails', () => { - def('removeTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AccessTokenManager#delete() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal($removeTokenObj) - }) - }) - }) -}) diff --git a/test/src/services/agent-service.test.js b/test/src/services/agent-service.test.js index 95b80bf2..35c0f968 100644 --- a/test/src/services/agent-service.test.js +++ b/test/src/services/agent-service.test.js @@ -6,7 +6,7 @@ const Validator = require('../../../src/schemas') const FogProvisionKeyManager = require('../../../src/data/managers/iofog-provision-key-manager') const MicroserviceManager = require('../../../src/data/managers/microservice-manager') const ioFogManager = require('../../../src/data/managers/iofog-manager') -const FogAccessTokenService = require('../../../src/services/iofog-access-token-service') +const FogKeyService = require('../../../src/services/iofog-key-service') const AppHelper = require('../../../src/helpers/app-helper') const ChangeTrackingService = require('../../../src/services/change-tracking-service') const MicroserviceStatusManager = require('../../../src/data/managers/microservice-status-manager') @@ -48,20 +48,23 @@ describe('Agent Service', () => { def('provisionResponse', () => 'provisionResponse') def('subject', () => $subject.agentProvision(provisionData, transaction)) - def('accessTokenResponse', () => Promise.resolve($accessTokenObj)) - + def('keyPairResponse', () => Promise.resolve({ + publicKey: 'testPublicKey', + privateKey: 'testPrivateKey', + })) + def('storePublicKeyResponse', () => Promise.resolve()) + def('changeTrackingUpdateResponse', () => Promise.resolve()) def('validatorResponse', () => Promise.resolve(true)) def('fogProvisionKeyManagerResponse', () => Promise.resolve({ - uuid: $uuid, + iofogUuid: $uuid, + expirationTime: new Date(Date.now() + 3600000), })) def('microserviceManagerResponse', () => Promise.resolve()) def('iofogManagerResponse', () => Promise.resolve({ uuid: $uuid, })) - def('fogAccessTokenServiceGenerateResponse', () => Promise.resolve({ - token: $token, - })) - def('fogAccessTokenServiceUpdateResponse', () => Promise.resolve()) + def('fogKeyServiceGenerateResponse', () => $keyPairResponse) + def('fogKeyServiceStoreResponse', () => $storePublicKeyResponse) def('iofogManagerUpdateResponse', () => Promise.resolve()) def('fogProvisionKeyManagerDeleteResponse', () => Promise.resolve()) @@ -70,8 +73,9 @@ describe('Agent Service', () => { $sandbox.stub(FogProvisionKeyManager, 'findOne').returns($fogProvisionKeyManagerResponse) $sandbox.stub(MicroserviceManager, 'findAllWithDependencies').returns($microserviceManagerResponse) $sandbox.stub(ioFogManager, 'findOne').returns($iofogManagerResponse) - $sandbox.stub(FogAccessTokenService, 'generateAccessToken').returns($fogAccessTokenServiceGenerateResponse) - $sandbox.stub(FogAccessTokenService, 'updateAccessToken').returns($fogAccessTokenServiceUpdateResponse) + $sandbox.stub(FogKeyService, 'generateKeyPair').returns($fogKeyServiceGenerateResponse) + $sandbox.stub(FogKeyService, 'storePublicKey').returns($fogKeyServiceStoreResponse) + $sandbox.stub(ChangeTrackingService, 'update').returns($changeTrackingUpdateResponse) $sandbox.stub(ioFogManager, 'update').returns($iofogManagerUpdateResponse) $sandbox.stub(FogProvisionKeyManager, 'delete').returns($fogProvisionKeyManagerDeleteResponse) }) @@ -111,7 +115,7 @@ describe('Agent Service', () => { it('calls ioFogManager.findOne with correct args', async () => { await $subject expect(ioFogManager.findOne).to.have.been.calledWith({ - uuid: $fogProvisionKeyManagerResponse.uuid, + uuid: $uuid, }, transaction) }) @@ -144,40 +148,38 @@ describe('Agent Service', () => { }) context('when MicroserviceManager#findAllWithDependencies succeeds', () => { - it('calls FogAccessTokenService.generateAccessToken with correct args', async () => { + it('calls FogKeyService.generateKeyPair with correct args', async () => { await $subject - expect(FogAccessTokenService.generateAccessToken).to.have.been.calledWith(transaction) + expect(FogKeyService.generateKeyPair).to.have.been.calledWith(transaction) }) - context('when FogAccessTokenService#generateAccessToken fails', () => { + context('when FogKeyService#generateKeyPair fails', () => { const error = 'Error!' - def('fogAccessTokenServiceGenerateResponse', () => Promise.reject(error)) + def('fogKeyServiceGenerateResponse', () => Promise.reject(error)) it(`fails with "${error}"`, () => { return expect($subject).to.be.rejectedWith(error) }) }) - context('when FogAccessTokenService#generateAccessToken succeeds', () => { - it('calls FogAccessTokenService.updateAccessToken with correct args', async () => { + context('when FogKeyService#generateKeyPair succeeds', () => { + it('calls FogKeyService.storePublicKey with correct args', async () => { await $subject - expect(FogAccessTokenService.updateAccessToken).to.have.been.calledWith($uuid, { - token: $token, - }, transaction) + expect(FogKeyService.storePublicKey).to.have.been.calledWith($uuid, 'testPublicKey', transaction) }) - context('when FogAccessTokenService#updateAccessToken fails', () => { + context('when FogKeyService#storePublicKey fails', () => { const error = 'Error!' - def('fogAccessTokenServiceUpdateResponse', () => Promise.reject(error)) + def('fogKeyServiceStoreResponse', () => Promise.reject(error)) it(`fails with "${error}"`, () => { return expect($subject).to.be.rejectedWith(error) }) }) - context('when FogAccessTokenService#updateAccessToken succeeds', () => { + context('when FogKeyService#storePublicKey succeeds', () => { it('calls ioFogManager.update with correct args', async () => { await $subject expect(ioFogManager.update).to.have.been.calledWith({ @@ -216,9 +218,10 @@ describe('Agent Service', () => { }) context('when FogProvisionKeyManager#delete succeeds', () => { - it(`succeeds`, () => { + it('succeeds', () => { return expect($subject).to.eventually.have.property('uuid') && - expect($subject).to.eventually.have.property('token') + expect($subject).to.eventually.have.property('privateKey') && + expect($subject).to.eventually.have.property('namespace') }) }) }) diff --git a/test/src/services/controller-service.test.js b/test/src/services/controller-service.test.js index a7e6e6a9..dff7a4ab 100644 --- a/test/src/services/controller-service.test.js +++ b/test/src/services/controller-service.test.js @@ -2,7 +2,7 @@ const { expect } = require('chai') const sinon = require('sinon') const ControllerService = require('../../../src/services/controller-service') -const ioFogTypesManager = require('../../../src/data/managers/iofog-type-manager') +const architectureManager = require('../../../src/data/managers/architecture-manager') const Config = require('../../../src/config') describe('Controller Service', () => { @@ -26,15 +26,15 @@ describe('Controller Service', () => { }])) beforeEach(() => { - $sandbox.stub(ioFogTypesManager, 'findAll').returns($findResponse) + $sandbox.stub(architectureManager, 'findAll').returns($findResponse) }) - it('calls ioFogTypesManager#findAll() with correct args', async () => { + it('calls architectureManager#findAll() with correct args', async () => { await $subject - expect(ioFogTypesManager.findAll).to.have.been.calledWith({}, transaction) + expect(architectureManager.findAll).to.have.been.calledWith({}, transaction) }) - context('when ioFogTypesManager#findAll() fails', () => { + context('when architectureManager#findAll() fails', () => { def('findResponse', () => Promise.reject(error)) it(`fails with ${error}`, () => { @@ -42,7 +42,7 @@ describe('Controller Service', () => { }) }) - context('when ioFogTypesManager#findAll() succeeds', () => { + context('when architectureManager#findAll() succeeds', () => { it('fulfills the promise', () => { return expect($subject).to.eventually.have.property('fogTypes') }) diff --git a/test/src/services/email-activation-code-service.test.js b/test/src/services/email-activation-code-service.test.js deleted file mode 100644 index c743514b..00000000 --- a/test/src/services/email-activation-code-service.test.js +++ /dev/null @@ -1,177 +0,0 @@ -const { expect } = require('chai') -const sinon = require('sinon') - -const EmailActivationCodeManager = require('../../../src/data/managers/email-activation-code-manager') -const EmailActivationCodeService = require('../../../src/services/email-activation-code-service') -const AppHelper = require('../../../src/helpers/app-helper') -const ErrorMessages = require('../../../src/helpers/error-messages') - - -describe('EmailActivationCode Service', () => { - def('subject', () => EmailActivationCodeService) - def('sandbox', () => sinon.createSandbox()) - - afterEach(() => $sandbox.restore()) - - describe('.generateActivationCode()', () => { - const transaction = {} - const error = 'Error!' - - const response = { - activationCode: 'abcdefgwdwdwdwdwd', - expirationTime: new Date().getTime() + ((60 * 60 * 24 * 3) * 1000), - } - - def('subject', () => $subject.generateActivationCode(transaction)) - def('generateStringResponse', () => response.activationCode) - def('findActivationCodeResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(AppHelper, 'generateRandomString').returns($generateStringResponse) - $sandbox.stub(EmailActivationCodeManager, 'getByActivationCode').returns($findActivationCodeResponse) - }) - - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - expect(AppHelper.generateRandomString).to.have.been.calledWith(16) - }) - - context('when AppHelper#generateRandomString() fails', () => { - def('generateStringResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('activationCode') - }) - }) - - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls EmailActivationCodeManager#getByActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeManager.getByActivationCode).to.have.been.calledWith(response.activationCode, - transaction) - }) - - context('when EmailActivationCodeManager#getByActivationCode() fails', () => { - def('findActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when EmailActivationCodeManager#getByActivationCode() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('activationCode') && - expect($subject).to.eventually.have.property('expirationTime') - }) - }) - }) - }) - - describe('.saveActivationCode()', () => { - const transaction = {} - const error = 'Error!' - - const userId = 15 - - const activationCodeData = { - activationCode: 'abcdefgwdwdwdwdwd', - expirationTime: new Date().getTime() + ((60 * 60 * 24 * 3) * 1000), - } - - def('subject', () => $subject.saveActivationCode(userId, activationCodeData, transaction)) - def('createActivationCodeResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(EmailActivationCodeManager, 'createActivationCode').returns($createActivationCodeResponse) - }) - - it('calls EmailActivationCodeManager#createActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeManager.createActivationCode).to.have.been.calledWith(userId, - activationCodeData.activationCode, activationCodeData.expirationTime, transaction) - }) - - context('when EmailActivationCodeManager#createActivationCode() fails', () => { - def('createActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(ErrorMessages.UNABLE_TO_CREATE_ACTIVATION_CODE) - }) - }) - - context('when EmailActivationCodeManager#createActivationCode() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal(undefined) - }) - }) - }) - - describe('.verifyActivationCode()', () => { - const transaction = {} - const error = 'Error!' - - const activationCode = 'abcdefgwdwdwdwdwd' - - def('subject', () => $subject.verifyActivationCode(activationCode, transaction)) - def('verifyActivationCodeResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(EmailActivationCodeManager, 'verifyActivationCode').returns($verifyActivationCodeResponse) - }) - - it('calls EmailActivationCodeManager#verifyActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeManager.verifyActivationCode).to.have.been.calledWith(activationCode, transaction) - }) - - context('when EmailActivationCodeManager#verifyActivationCode() fails', () => { - def('verifyActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(ErrorMessages.UNABLE_TO_GET_ACTIVATION_CODE) - }) - }) - - context('when EmailActivationCodeManager#verifyActivationCode() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal(undefined) - }) - }) - }) - - describe('.deleteActivationCode()', () => { - const transaction = {} - const error = 'Error!' - - const activationCode = 'abcdefgwdwdwdwdwd' - - def('subject', () => $subject.deleteActivationCode(activationCode, transaction)) - def('deleteActivationCodeResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(EmailActivationCodeManager, 'delete').returns($deleteActivationCodeResponse) - }) - - it('calls EmailActivationCodeManager#delete() with correct args', async () => { - await $subject - expect(EmailActivationCodeManager.delete).to.have.been.calledWith({ - activationCode: activationCode, - }, transaction) - }) - - context('when EmailActivationCodeManager#delete() fails', () => { - def('deleteActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when EmailActivationCodeManager#delete() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal(undefined) - }) - }) - }) -}) diff --git a/test/src/services/microservices-service.test.js b/test/src/services/microservices-service.test.js index a9c767b3..c3c86701 100644 --- a/test/src/services/microservices-service.test.js +++ b/test/src/services/microservices-service.test.js @@ -19,13 +19,15 @@ const MicroserviceEnvManager = require('../../../src/data/managers/microservice- const MicroserviceArgManager = require('../../../src/data/managers/microservice-arg-manager') const RegistryManager = require('../../../src/data/managers/registry-manager') const Op = require('sequelize').Op -const MicroservicePublicPortManager = require('../../../src/data/managers/microservice-public-port-manager') +const MicroservicePublicPortManager = { + findAll: () => Promise.resolve([]), + create: () => Promise.resolve(), + updateOrCreate: () => Promise.resolve() +} const ioFogManager = require('../../../src/data/managers/iofog-manager') const ioFogService = require('../../../src/services/iofog-service') const Errors = require('../../../src/helpers/errors') -const constants = require('../../../src/config/constants') const Constants = require('../../../src/helpers/constants') -const { application } = require('express') describe('Microservices Service', () => { def('subject', () => MicroservicesService) diff --git a/test/src/services/user-service.test.js b/test/src/services/user-service.test.js deleted file mode 100644 index 71311004..00000000 --- a/test/src/services/user-service.test.js +++ /dev/null @@ -1,814 +0,0 @@ -const { expect } = require('chai') -const sinon = require('sinon') - -const UserManager = require('../../../src/data/managers/user-manager') -const UserService = require('../../../src/services/user-service') -const Config = require('../../../src/config') -const AccessTokenService = require('../../../src/services/access-token-service') -const Validator = require('../../../src/schemas') -const AppHelper = require('../../../src/helpers/app-helper') -const ioFogManager = require('../../../src/data/managers/iofog-manager') -const EmailActivationCodeService = require('../../../src/services/email-activation-code-service') -const nodemailer = require('nodemailer') - -describe('User Service', () => { - def('subject', () => UserService) - def('sandbox', () => sinon.createSandbox()) - - const isCLI = false - - afterEach(() => $sandbox.restore()) - - describe('.signUp()', () => { - const transaction = {} - const error = 'Error!' - - const newUser = { - id: 16, - firstName: 'testFirstName', - lastName: 'testLastName', - email: 'testEmail', - emailActivated: true, - } - - const response = { - userId: 16, - firstName: newUser.firstName, - lastName: newUser.lastName, - email: newUser.email, - emailActivated: newUser.emailActivated, - } - - def('subject', () => $subject.signUp(newUser, isCLI, transaction)) - def('configGetResponse', () => false) - def('findUserResponse', () => Promise.resolve()) - def('createUserResponse', () => Promise.resolve(newUser)) - - beforeEach(() => { - $sandbox.stub(Config, 'get').returns($configGetResponse) - $sandbox.stub(UserManager, 'findOne').returns($findUserResponse) - $sandbox.stub(UserManager, 'create').returns($createUserResponse) - }) - - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:ActivationEnabled') - }) - - context('when Config#get() succeeds', () => { - it('calls UserManager#findOne() with correct args', async () => { - await $subject - expect(UserManager.findOne).to.have.been.calledWith({ - email: newUser.email, - }, transaction) - }) - - context('when UserManager#findOne() fails', () => { - def('findUserResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#findOne() succeeds', () => { - it('calls UserManager#create() with correct args', async () => { - await $subject - expect(UserManager.create).to.have.been.calledWith(newUser, transaction) - }) - - context('when UserManager#create() fails', () => { - def('createUserResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#create() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal(response) - }) - }) - }) - }) - }) - - describe('.login()', () => { - const transaction = {} - const error = 'Error!' - - const credentials = { - email: 'testEmail', - password: 'testPassword', - } - - const user = { - email: 'testEmail', - password: 'testPassword', - id: 15, - } - - const testAccessToken = 'testAccessToken' - - const configGet2 = 155 - const date = 1555 - const tokenExpireTime = date + (configGet2 * 1000) - - const createToken = { - token: testAccessToken, - expirationTime: tokenExpireTime, - userId: user.id, - } - - - def('subject', () => $subject.login(credentials, isCLI, transaction)) - def('findUserResponse', () => Promise.resolve(user)) - def('decryptTextResponse', () => credentials.password) - def('getConfigResponse', () => false) - def('getConfigResponse2', () => configGet2) - def('generateAccessTokenResponse', () => 'testAccessToken') - def('findByAccessTokenResponse', () => false) - def('createAccessTokenResponse', () => Promise.resolve({ - token: 'token', - })) - def('dateResponse', () => date) - - beforeEach(() => { - $sandbox.stub(UserManager, 'findOne').returns($findUserResponse) - $sandbox.stub(AppHelper, 'decryptText').returns($decryptTextResponse) - $sandbox.stub(Config, 'get') - .onFirstCall().returns($getConfigResponse) - .onSecondCall().returns($getConfigResponse2) - $sandbox.stub(AppHelper, 'generateAccessToken').returns($generateAccessTokenResponse) - $sandbox.stub(UserManager, 'findByAccessToken').returns($findByAccessTokenResponse) - $sandbox.stub(AccessTokenService, 'createAccessToken').returns($createAccessTokenResponse) - $sandbox.stub(Date.prototype, 'getTime').returns($dateResponse) - }) - - it('calls UserManager#findOne() with correct args', async () => { - await $subject - expect(UserManager.findOne).to.have.been.calledWith({ - email: credentials.email, - }, transaction) - }) - - context('when UserManager#findOne() fails', () => { - def('findUserResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#findOne() succeeds', () => { - it('calls AppHelper#decryptText() with correct args', async () => { - await $subject - expect(AppHelper.decryptText).to.have.been.calledWith(user.password, user.email) - }) - - context('when AppHelper#decryptText() fails', () => { - const err = 'Invalid credentials' - def('decryptTextResponse', () => Promise.reject(err)) - - it(`fails with ${err}`, () => { - return expect($subject).to.be.rejectedWith(err) - }) - }) - - context('when AppHelper#decryptText() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:ActivationEnabled') - }) - - context('when Config#get() fails', () => { - const err = 'Email is not activated. Please activate your account first.' - def('getConfigResponse', () => Promise.reject(err)) - - it(`fails with ${err}`, () => { - return expect($subject).to.be.rejectedWith(err) - }) - }) - - context('when Config#get() succeeds', () => { - it('calls AppHelper#generateAccessToken() with correct args', async () => { - await $subject - expect(AppHelper.generateAccessToken).to.have.been.calledWith() - }) - - context('when AppHelper#generateAccessToken() fails', () => { - def('generateAccessTokenResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('accessToken') - }) - }) - - context('when AppHelper#generateAccessToken() succeeds', () => { - it('calls UserManager#findByAccessToken() with correct args', async () => { - await $subject - expect(UserManager.findByAccessToken).to.have.been.calledWith(testAccessToken, transaction) - }) - - context('when UserManager#findByAccessToken() fails', () => { - def('findByAccessTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#findByAccessToken() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Settings:UserTokenExpirationIntervalSeconds') - }) - - context('when Config#get() fails', () => { - def('getConfigResponse2', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('accessToken') - }) - }) - - context('when Config#get() succeeds', () => { - it('calls AccessTokenService#createAccessToken() with correct args', async () => { - await $subject - expect(AccessTokenService.createAccessToken).to.have.been.calledWith(createToken, transaction) - }) - - context('when AccessTokenService#createAccessToken() fails', () => { - def('createAccessTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AccessTokenService#createAccessToken() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('accessToken') - }) - }) - }) - }) - }) - }) - }) - }) - }) - - describe('.resendActivation()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - email: 'testEmail', - password: 'testPassword', - id: 15, - } - - const emailObj = { - email: 'testEmail', - } - - const activationCodeData = {} - - const mailer = { - sendMail: function(options) { - }, - } - - def('subject', () => $subject.resendActivation(emailObj, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findUserResponse', () => Promise.resolve(user)) - def('generateActivationCodeResponse', () => Promise.resolve({})) - def('saveActivationCodeResponse', () => Promise.resolve()) - def('getEmailAddressResponse', () => 'test@test.com') - def('getEmailPasswordResponse', () => 'test') - def('getEmailServiceResponse', () => 'SendGrid') - def('getEmailHomeUrlResponse', () => 'test') - def('decryptTextResponse', () => 'test') - def('createTransportResponse', () => mailer) - def('sendMailResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(UserManager, 'findOne').returns($findUserResponse) - $sandbox.stub(EmailActivationCodeService, 'generateActivationCode').returns($generateActivationCodeResponse) - $sandbox.stub(EmailActivationCodeService, 'saveActivationCode').returns($saveActivationCodeResponse) - $sandbox.stub(Config, 'get') - .onCall(0).returns($getEmailAddressResponse) - .onCall(1).returns($getEmailPasswordResponse) - .onCall(2).returns($getEmailAddressResponse) - .onCall(3).returns($getEmailServiceResponse) - .onCall(4).returns($getEmailHomeUrlResponse) - $sandbox.stub(AppHelper, 'decryptText').returns($decryptTextResponse) - $sandbox.stub(nodemailer, 'createTransport').returns($createTransportResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(emailObj, Validator.schemas.resendActivation) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls UserManager#findOne() with correct args', async () => { - await $subject - expect(UserManager.findOne).to.have.been.calledWith(emailObj, transaction) - }) - - context('when UserManager#findOne() fails', () => { - def('findUserResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#findOne() succeeds', () => { - it('calls EmailActivationCodeService#generateActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeService.generateActivationCode).to.have.been.calledWith(transaction) - }) - context('when EmailActivationCodeService#generateActivationCode() fails', () => { - def('generateActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when EmailActivationCodeService#generateActivationCode() succeeds', () => { - it('calls EmailActivationCodeService#saveActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeService.saveActivationCode).to.have.been.calledWith( - user.id, - activationCodeData, - transaction) - }) - - context('when EmailActivationCodeService#saveActivationCode() fails', () => { - def('saveActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when EmailActivationCodeService#saveActivationCode() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:Address') - }) - - context('when Config#get() fails', () => { - def('getEmailAddressResponse', Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - context('when Config#get() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:Password') - }) - - context('when Config#get() fails', () => { - def('getEmailPasswordResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - context('when Config#get() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:Address') - }) - - context('when Config#get() fails', () => { - def('getEmailAddressResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - context('when Config#get() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:Service') - }) - - context('when Config#get() fails', () => { - def('getEmailServiceResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - context('when Config#get() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - - - describe('.activateUser()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - email: 'testEmail', - password: 'testPassword', - id: 15, - } - - const updatedObj = { - emailActivated: true, - } - - const codeData = { - activationCode: 'testActivationCode', - } - - def('subject', () => $subject.activateUser(codeData, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('verifyActivationCodeResponse', () => Promise.resolve({ - userId: user.id, - })) - def('updateUserResponse', () => Promise.resolve()) - def('deleteActivationCodeResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(EmailActivationCodeService, 'verifyActivationCode').returns($verifyActivationCodeResponse) - $sandbox.stub(UserManager, 'update').returns($updateUserResponse) - $sandbox.stub(EmailActivationCodeService, 'deleteActivationCode').returns($deleteActivationCodeResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(codeData, Validator.schemas.activateUser) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls EmailActivationCodeService#verifyActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeService.verifyActivationCode).to.have.been.calledWith(codeData.activationCode, transaction) - }) - - context('when EmailActivationCodeService#verifyActivationCode() fails', () => { - def('verifyActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when EmailActivationCodeService#verifyActivationCode() succeeds', () => { - it('calls UserManager#update() with correct args', async () => { - await $subject - expect(UserManager.update).to.have.been.calledWith({ - id: user.id, - }, updatedObj, transaction) - }) - - context('when UserManager#update() fails', () => { - const err = 'User not updated' - def('updateUserResponse', () => Promise.reject(err)) - - it(`fails with ${err}`, () => { - return expect($subject).to.be.rejectedWith(err) - }) - }) - - context('when UserManager#update() succeeds', () => { - it('calls EmailActivationCodeService#deleteActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeService.deleteActivationCode).to.have.been.calledWith(codeData.activationCode, - transaction) - }) - - context('when EmailActivationCodeService#deleteActivationCode() fails', () => { - def('deleteActivationCodeResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when EmailActivationCodeService#deleteActivationCode() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) - - describe('.logout()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - def('subject', () => $subject.logout(user, isCLI, transaction)) - def('removeAccessTokenResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(AccessTokenService, 'removeAccessTokenByUserId').returns($removeAccessTokenResponse) - }) - - it('calls AccessTokenService#removeAccessTokenByUserId() with correct args', async () => { - await $subject - expect(AccessTokenService.removeAccessTokenByUserId).to.have.been.calledWith(user.id, transaction) - }) - - context('when AccessTokenService#removeAccessTokenByUserId() fails', () => { - def('removeAccessTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AccessTokenService#removeAccessTokenByUserId() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('.updateUserDetails()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - email: 'testEmail', - password: 'testPassword', - id: 15, - } - - const profileData = { - firstName: 'testFirstName', - lastName: 'testLastName', - } - - def('subject', () => $subject.updateUserDetails(user, profileData, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse', () => profileData) - def('updateDetailsResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(UserManager, 'updateDetails').returns($updateDetailsResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(profileData, Validator.schemas.updateUserProfile) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(profileData) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('firstName') - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls UserManager#updateDetails() with correct args', async () => { - await $subject - expect(UserManager.updateDetails).to.have.been.calledWith(user, profileData, transaction) - }) - - context('when UserManager#updateDetails() fails', () => { - def('updateUserResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('firstName') - }) - }) - - context('when UserManager#updateDetails() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('firstName') - }) - }) - }) - }) - }) - - describe('.deleteUser()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - email: 'testEmail', - password: 'testPassword', - id: 15, - } - - const profileData = { - firstName: 'testFirstName', - lastName: 'testLastName', - } - - const force = false - - def('subject', () => $subject.deleteUser(force, user, isCLI, transaction)) - def('findAllResponse', () => Promise.resolve([{}])) - def('deleteUserResponse', () => profileData) - - beforeEach(() => { - $sandbox.stub(ioFogManager, 'findAll').returns($findAllResponse) - $sandbox.stub(UserManager, 'delete').returns($deleteUserResponse) - }) - - it('calls ioFogManager#findAll() with correct args', async () => { - await $subject - expect(ioFogManager.findAll).to.have.been.calledWith({ - userId: user.id, - }, transaction) - }) - - context('when ioFogManager#findAll() fails', () => { - def('findAllResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findAll() succeeds', () => { - it('calls UserManager#delete() with correct args', async () => { - await $subject - expect(UserManager.delete).to.have.been.calledWith({ - id: user.id, - }, transaction) - }) - - context('when UserManager#delete() fails', () => { - def('deleteUserResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when UserManager#delete() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - - // TODO updateUserPassword, resetUserPassword with rewire - - describe('.list()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const response = [{ - id: user.id, - }] - - const attributes = { exclude: ['password'] } - - def('subject', () => $subject.list(isCLI, transaction)) - def('findAllResponse', () => Promise.resolve(response)) - - beforeEach(() => { - $sandbox.stub(UserManager, 'findAllWithAttributes').returns($findAllResponse) - }) - - it('calls UserManager#findAllWithAttributes() with correct args', async () => { - await $subject - expect(UserManager.findAllWithAttributes).to.have.been.calledWith({}, attributes, transaction) - }) - - context('when UserManager#findAllWithAttributes() fails', () => { - def('findAllResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#findAllWithAttributes() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(response) - }) - }) - }) - - describe('.suspendUser()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const updatedObj = { - emailActivated: false, - } - - def('subject', () => $subject.suspendUser(user, isCLI, transaction)) - def('removeAccessTokenResponse', () => Promise.resolve()) - def('updateUserResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(AccessTokenService, 'removeAccessTokenByUserId').returns($removeAccessTokenResponse) - $sandbox.stub(UserManager, 'update').returns($updateUserResponse) - }) - - it('calls AccessTokenService#removeAccessTokenByUserId() with correct args', async () => { - await $subject - expect(AccessTokenService.removeAccessTokenByUserId).to.have.been.calledWith(user.id, transaction) - }) - - context('when AccessTokenService#removeAccessTokenByUserId() fails', () => { - def('removeAccessTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AccessTokenService#removeAccessTokenByUserId() succeeds', () => { - it('calls UserManager#update() with correct args', async () => { - await $subject - expect(UserManager.update).to.have.been.calledWith({ - id: user.id, - }, updatedObj, transaction) - }) - - context('when UserManager#update() fails', () => { - def('removeAccessTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) -}) From 6dccde4a97a122b63ac2fd546c57b4272b72d9c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 03:43:03 +0300 Subject: [PATCH 07/75] Align agent schemas with Edgelet wire contract Rename container engine config keys, add availableRuntimes and phase flags to status, replace fogTypeId with archId, and restrict application to string-only validation. --- src/data/managers/iofog-manager.js | 9 +++++++++ src/data/models/fog.js | 5 +++++ src/schemas/agent.js | 13 ++++++++----- src/schemas/catalog.js | 13 ++++++------- src/schemas/iofog.js | 18 +++++++++--------- src/schemas/microservice.js | 7 +------ 6 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/data/managers/iofog-manager.js b/src/data/managers/iofog-manager.js index a0e98c76..3e8a32e7 100644 --- a/src/data/managers/iofog-manager.js +++ b/src/data/managers/iofog-manager.js @@ -16,6 +16,7 @@ const models = require('../models') const Fog = models.Fog const Tags = models.Tags +const Architecture = models.Architecture const Microservice = models.Microservice const Strace = models.StraceDiagnostics @@ -34,6 +35,10 @@ class FogManager extends BaseManager { through: { attributes: [] } + }, + { model: Architecture, + as: 'architecture', + attributes: ['id', 'name', 'image', 'description'] } ] }, { @@ -50,6 +55,10 @@ class FogManager extends BaseManager { through: { attributes: [] } + }, + { model: Architecture, + as: 'architecture', + attributes: ['id', 'name', 'image', 'description'] } ] }, { transaction }) diff --git a/src/data/models/fog.js b/src/data/models/fog.js index b16b03cb..6ce2c0e6 100644 --- a/src/data/models/fog.js +++ b/src/data/models/fog.js @@ -351,6 +351,11 @@ module.exports = (sequelize, DataTypes) => { gpsStatus: { type: DataTypes.TEXT, field: 'gps_status' + }, + archId: { + type: DataTypes.INTEGER, + defaultValue: 0, + field: 'arch_id' } }, { tableName: 'Fogs', diff --git a/src/schemas/agent.js b/src/schemas/agent.js index 40bd2046..d5f5629d 100644 --- a/src/schemas/agent.js +++ b/src/schemas/agent.js @@ -40,7 +40,7 @@ const updateAgentConfig = { 'type': 'object', 'properties': { 'networkInterface': { 'type': 'string' }, - 'dockerUrl': { 'type': 'string' }, + 'containerEngineUrl': { 'type': 'string' }, 'diskLimit': { 'type': 'integer', 'minimum': 0 }, 'diskDirectory': { 'type': 'string' }, 'memoryLimit': { 'type': 'integer', 'minimum': 0 }, @@ -58,7 +58,7 @@ const updateAgentConfig = { 'gpsDevice': { 'type': 'string' }, 'gpsScanFrequency': { 'type': 'integer', 'minimum': 0 }, 'edgeGuardFrequency': { 'type': 'integer', 'minimum': 0 }, - 'dockerPruningFrequency': { 'type': 'integer', 'minimum': 0 }, + 'pruningFrequency': { 'type': 'integer', 'minimum': 0 }, 'availableDiskThreshold': { 'type': 'integer', 'minimum': 0 }, 'logLevel': { 'type': 'string' }, 'timeZone': { 'type': 'string' } @@ -102,10 +102,13 @@ const updateAgentStatus = { 'lastStatusTime': { 'type': 'integer', 'minimum': 0 }, 'ipAddress': { 'type': 'string' }, 'ipAddressExternal': { 'type': 'string' }, - 'processedMessages': { 'type': 'integer', 'minimum': 0 }, - 'microserviceMessageCounts': { 'type': 'string' }, - 'messageSpeed': { 'type': 'number', 'minimum': 0 }, 'lastCommandTime': { 'type': 'integer', 'minimum': 0 }, + 'availableRuntimes': { + 'type': 'array', + 'items': { 'type': 'string' } + }, + 'runtimeAgentPhase': { 'type': 'string' }, + 'controlPlaneQuiesced': { 'type': 'boolean' }, 'gpsMode': { 'type': 'string' }, 'gpsDevice': { 'type': 'string' }, 'gpsScanFrequency': { 'type': 'integer', 'minimum': 0 }, diff --git a/src/schemas/catalog.js b/src/schemas/catalog.js index 4b61d9a1..58fb3a1e 100644 --- a/src/schemas/catalog.js +++ b/src/schemas/catalog.js @@ -68,14 +68,13 @@ const image = { 'type': 'object', 'properties': { 'containerImage': { 'type': 'string' }, - 'fogTypeId': - { - 'type': 'integer', - 'minimum': 1, - 'maximum': 2 - } + 'archId': { + 'type': 'integer', + 'minimum': 1, + 'maximum': 4 + } }, - 'required': ['containerImage', 'fogTypeId'], + 'required': ['containerImage', 'archId'], 'additionalProperties': true } diff --git a/src/schemas/iofog.js b/src/schemas/iofog.js index aaa05d30..9d9a7733 100644 --- a/src/schemas/iofog.js +++ b/src/schemas/iofog.js @@ -21,8 +21,8 @@ const iofogCreate = { 'longitude': { 'type': 'number', 'minimum': -180, 'maximum': 180 }, 'description': { 'type': 'string' }, 'networkInterface': { 'type': 'string' }, - 'dockerUrl': { 'type': 'string' }, - 'containerEngine': { 'type': 'string', 'enum': ['docker', 'podman'] }, + 'containerEngineUrl': { 'type': 'string' }, + 'containerEngine': { 'type': 'string', 'enum': ['edgelet', 'docker', 'podman'] }, 'deploymentType': { 'type': 'string', 'enum': ['native', 'container'] }, 'diskLimit': { 'type': 'integer', 'minimum': 0 }, 'diskDirectory': { 'type': 'string' }, @@ -37,8 +37,8 @@ const iofogCreate = { 'bluetoothEnabled': { 'type': 'boolean' }, 'watchdogEnabled': { 'type': 'boolean' }, 'abstractedHardwareEnabled': { 'type': 'boolean' }, - 'fogType': { 'type': 'integer', 'minimum': 0, 'maximum': 2 }, - 'dockerPruningFrequency': { 'type': 'integer', 'minimum': 0 }, + 'archId': { 'type': 'integer', 'minimum': 0, 'maximum': 4 }, + 'pruningFrequency': { 'type': 'integer', 'minimum': 0 }, 'availableDiskThreshold': { 'type': 'integer', 'minimum': 0 }, 'logLevel': { 'type': 'string' }, 'isSystem': { 'type': 'boolean' }, @@ -84,7 +84,7 @@ const iofogCreate = { } ], 'additionalProperties': true, - 'required': ['name', 'fogType'] + 'required': ['name', 'archId'] } const iofogUpdate = { @@ -98,8 +98,8 @@ const iofogUpdate = { 'longitude': { 'type': 'number', 'minimum': -180, 'maximum': 180 }, 'description': { 'type': 'string' }, 'networkInterface': { 'type': 'string' }, - 'dockerUrl': { 'type': 'string' }, - 'containerEngine': { 'type': 'string', 'enum': ['docker', 'podman'] }, + 'containerEngineUrl': { 'type': 'string' }, + 'containerEngine': { 'type': 'string', 'enum': ['edgelet', 'docker', 'podman'] }, 'deploymentType': { 'type': 'string', 'enum': ['native', 'container'] }, 'diskLimit': { 'type': 'integer', 'minimum': 0 }, 'diskDirectory': { 'type': 'string' }, @@ -114,8 +114,8 @@ const iofogUpdate = { 'bluetoothEnabled': { 'type': 'boolean' }, 'watchdogEnabled': { 'type': 'boolean' }, 'abstractedHardwareEnabled': { 'type': 'boolean' }, - 'fogType': { 'type': 'integer', 'minimum': 0, 'maximum': 2 }, - 'dockerPruningFrequency': { 'type': 'integer', 'minimum': 0 }, + 'archId': { 'type': 'integer', 'minimum': 0, 'maximum': 4 }, + 'pruningFrequency': { 'type': 'integer', 'minimum': 0 }, 'availableDiskThreshold': { 'type': 'integer', 'minimum': 0 }, 'logLevel': { 'type': 'string' }, 'isSystem': { 'type': 'boolean' }, diff --git a/src/schemas/microservice.js b/src/schemas/microservice.js index 56872ab3..c5a17637 100644 --- a/src/schemas/microservice.js +++ b/src/schemas/microservice.js @@ -22,12 +22,7 @@ const microserviceCreate = { 'registryId': { 'type': 'integer' }, - 'application': { - 'anyOf': [ - { 'type': 'string' }, - { 'type': 'number' } - ] - }, + 'application': { 'type': 'string' }, 'iofogUuid': { 'type': 'string' }, 'agentName': { 'type': 'string' }, 'hostNetworkMode': { 'type': 'boolean' }, From 47b571815b2f1d1d054129fff2c9c0054e5a2a14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 03:43:07 +0300 Subject: [PATCH 08/75] Update agent and ioFog services for Edgelet field names Persist availableRuntimes on status ingest, map containerEngineUrl and pruningFrequency on config, parse provision engine, and expose getArchitectures instead of getFogTypes. --- src/services/agent-service.js | 33 +++++++++++--------- src/services/controller-service.js | 18 +++++------ src/services/iofog-service.js | 48 +++++++++++++++++++----------- 3 files changed, 59 insertions(+), 40 deletions(-) diff --git a/src/services/agent-service.js b/src/services/agent-service.js index 485bbcc1..b5e494f0 100644 --- a/src/services/agent-service.js +++ b/src/services/agent-service.js @@ -91,11 +91,16 @@ const agentProvision = async function (provisionData, transaction) { // Store the public key await FogKeyService.storePublicKey(fog.uuid, keyPair.publicKey, transaction) + const provisionUpdate = { + archId: provisionData.type + } + if (provisionData.engine) { + provisionUpdate.containerEngine = provisionData.engine + } + await FogManager.update({ uuid: fog.uuid - }, { - fogTypeId: provisionData.type - }, transaction) + }, provisionUpdate, transaction) await FogProvisionKeyManager.delete({ provisionKey: provisionData.key @@ -150,7 +155,7 @@ const getAgentConfig = async function (fog, transaction) { }, transaction) const resp = { networkInterface: fogData.networkInterface, - dockerUrl: fogData.dockerUrl, + containerEngineUrl: fogData.dockerUrl, diskLimit: fogData.diskLimit, diskDirectory: fogData.diskDirectory, memoryLimit: fogData.memoryLimit, @@ -170,7 +175,7 @@ const getAgentConfig = async function (fog, transaction) { longitude: fogData.longitude, logLevel: fogData.logLevel, availableDiskThreshold: fogData.availableDiskThreshold, - dockerPruningFrequency: fogData.dockerPruningFrequency, + pruningFrequency: fogData.dockerPruningFrequency, timeZone: fogData.timeZone } return resp @@ -181,7 +186,7 @@ const updateAgentConfig = async function (updateData, fog, transaction) { let update = { networkInterface: updateData.networkInterface, - dockerUrl: updateData.dockerUrl, + dockerUrl: updateData.containerEngineUrl, diskLimit: updateData.diskLimit, diskDirectory: updateData.diskDirectory, memoryLimit: updateData.memoryLimit, @@ -199,7 +204,7 @@ const updateAgentConfig = async function (updateData, fog, transaction) { gpsDevice: updateData.gpsDevice, gpsScanFrequency: updateData.gpsScanFrequency, edgeGuardFrequency: updateData.edgeGuardFrequency, - dockerPruningFrequency: updateData.dockerPruningFrequency, + dockerPruningFrequency: updateData.pruningFrequency, availableDiskThreshold: updateData.availableDiskThreshold, logLevel: updateData.logLevel, timeZone: updateData.timeZone @@ -268,9 +273,9 @@ const updateAgentStatus = async function (agentStatus, fog, transaction) { lastStatusTime: agentStatus.lastStatusTime, ipAddress: agentStatus.ipAddress, ipAddressExternal: agentStatus.ipAddressExternal, - processedMessages: agentStatus.processedMessages, - microserviceMessageCounts: agentStatus.microserviceMessageCounts, - messageSpeed: agentStatus.messageSpeed, + availableRuntimes: agentStatus.availableRuntimes != null + ? JSON.stringify(agentStatus.availableRuntimes) + : undefined, lastCommandTime: agentStatus.lastCommandTime, tunnelStatus: agentStatus.tunnelStatus, version: agentStatus.version, @@ -374,12 +379,12 @@ async function _resolveServiceAccountRules (serviceAccount, transaction) { const getAgentMicroservices = async function (fog, transaction) { const microservices = await MicroserviceManager.findAllActiveApplicationMicroservices(fog.uuid, transaction) - const fogTypeId = fog.fogTypeId + const archId = fog.archId const response = [] for (const microservice of microservices) { const images = (microservice.images && microservice.images.length > 0) ? microservice.images : microservice.catalogItem.images - const image = images.find((image) => image.fogTypeId === fogTypeId) + const image = images.find((image) => image.archId === archId) const imageId = image ? image.containerImage : '' if (!imageId || imageId === '') { continue @@ -702,7 +707,7 @@ const _saveSnapShot = function (req, form, fog, transaction) { }) } -async function _checkMicroservicesFogType (fog, fogTypeId, transaction) { +async function _checkMicroservicesFogType (fog, archId, transaction) { const where = { iofogUuid: fog.uuid } @@ -714,7 +719,7 @@ async function _checkMicroservicesFogType (fog, fogTypeId, transaction) { let exists = false const images = (microservice.images && microservice.images.length > 0) ? microservice.images : microservice.catalogItem.images for (const image of images) { - if (image.fogTypeId === fogTypeId) { + if (image.archId === archId) { exists = true break } diff --git a/src/services/controller-service.js b/src/services/controller-service.js index e0513035..dfe70d97 100644 --- a/src/services/controller-service.js +++ b/src/services/controller-service.js @@ -16,21 +16,21 @@ const TransactionDecorator = require('../decorators/transaction-decorator') const packageJson = require('../../package') const AppHelper = require('../helpers/app-helper') -const getFogTypes = async function (isCLI, transaction) { - const ioFogTypes = await architectureManager.findAll({}, transaction) +const getArchitectures = async function (isCLI, transaction) { + const architectures = await architectureManager.findAll({}, transaction) const response = [] - for (const ioFogType of ioFogTypes) { + for (const architecture of architectures) { response.push({ - id: ioFogType.id, - name: ioFogType.name, - image: ioFogType.image, - description: ioFogType.description + id: architecture.id, + name: architecture.name, + image: architecture.image, + description: architecture.description }) } return { - fogTypes: response + architectures: response } } @@ -59,7 +59,7 @@ const getVersion = async function (isCLI) { } module.exports = { - getFogTypes: TransactionDecorator.generateTransaction(getFogTypes), + getArchitectures: TransactionDecorator.generateTransaction(getArchitectures), statusController: statusController, getVersion: getVersion } diff --git a/src/services/iofog-service.js b/src/services/iofog-service.js index 8b4fa151..ffe794c8 100644 --- a/src/services/iofog-service.js +++ b/src/services/iofog-service.js @@ -63,6 +63,11 @@ const SITE_CA_CERT = 'router-site-ca' const DEFAULT_ROUTER_LOCAL_CA = 'default-router-local-ca' const SERVICE_ANNOTATION_TAG = 'service.datasance.com/tag' +function _resolveArchId (fogData) { + if (fogData.archId !== undefined) return fogData.archId + return undefined +} + async function checkKubernetesEnvironment () { const controlPlane = process.env.CONTROL_PLANE || config.get('app.ControlPlane') return controlPlane && controlPlane.toLowerCase() === 'kubernetes' @@ -288,7 +293,7 @@ async function createFogEndPoint (fogData, isCLI, transaction) { // gpsMode: fogData.latitude || fogData.longitude ? 'manual' : undefined, description: fogData.description, networkInterface: fogData.networkInterface, - dockerUrl: fogData.dockerUrl, + dockerUrl: fogData.containerEngineUrl, containerEngine: fogData.containerEngine, deploymentType: fogData.deploymentType, diskLimit: fogData.diskLimit, @@ -304,10 +309,10 @@ async function createFogEndPoint (fogData, isCLI, transaction) { bluetoothEnabled: fogData.bluetoothEnabled, watchdogEnabled: fogData.watchdogEnabled, abstractedHardwareEnabled: fogData.abstractedHardwareEnabled, - fogTypeId: fogData.fogType, + archId: _resolveArchId(fogData), logLevel: fogData.logLevel, edgeGuardFrequency: fogData.edgeGuardFrequency, - dockerPruningFrequency: fogData.dockerPruningFrequency, + dockerPruningFrequency: fogData.pruningFrequency, availableDiskThreshold: fogData.availableDiskThreshold, isSystem: fogData.isSystem, host: fogData.host, @@ -486,7 +491,7 @@ async function updateFogEndPoint (fogData, isCLI, transaction) { // gpsMode: fogData.latitude || fogData.longitude ? 'manual' : undefined, description: fogData.description, networkInterface: fogData.networkInterface, - dockerUrl: fogData.dockerUrl, + dockerUrl: fogData.containerEngineUrl, containerEngine: fogData.containerEngine, deploymentType: fogData.deploymentType, diskLimit: fogData.diskLimit, @@ -503,9 +508,9 @@ async function updateFogEndPoint (fogData, isCLI, transaction) { watchdogEnabled: fogData.watchdogEnabled, isSystem: fogData.isSystem, abstractedHardwareEnabled: fogData.abstractedHardwareEnabled, - fogTypeId: fogData.fogType, + archId: _resolveArchId(fogData), logLevel: fogData.logLevel, - dockerPruningFrequency: fogData.dockerPruningFrequency, + dockerPruningFrequency: fogData.pruningFrequency, edgeGuardFrequency: fogData.edgeGuardFrequency, host: fogData.host, availableDiskThreshold: fogData.availableDiskThreshold, @@ -546,11 +551,12 @@ async function updateFogEndPoint (fogData, isCLI, transaction) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_SYSTEM_CHANGE)) } - // Prevent overwriting detected fogType (1 or 2) with "auto" (0) - // If fogType is being set to "auto" (0) but the agent has already detected its type (1 or 2), + // Prevent overwriting detected arch (1 or 2) with "auto" (0) + // If arch is being set to "auto" (0) but the agent has already detected its type (1 or 2), // preserve the detected type to ensure getAgentMicroservices can find matching images - if (fogData.fogType === 0 && (oldFog.fogTypeId === 1 || oldFog.fogTypeId === 2)) { - updateFogData.fogTypeId = undefined + const requestedArchId = _resolveArchId(fogData) + if (requestedArchId === 0 && (oldFog.archId === 1 || oldFog.archId === 2)) { + updateFogData.archId = undefined // Remove undefined fields again after modifying updateFogData updateFogData = AppHelper.deleteUndefinedFields(updateFogData) } @@ -956,7 +962,15 @@ async function _getFogExtraInformation (fog, transaction) { if (fog.toJSON && typeof fog.toJSON === 'function') { fog = fog.toJSON() } - return { ...fog, tags: _mapTags(fog), ...routerConfig, ...natsConfig, edgeResources, volumeMounts } + const { fogType, fogTypeId, architecture, ...fogFields } = fog + const archId = fogFields.archId + const arch = architecture ? { + id: architecture.id, + name: architecture.name, + image: architecture.image, + description: architecture.description + } : undefined + return { ...fogFields, archId, arch, tags: _mapTags(fog), ...routerConfig, ...natsConfig, edgeResources, volumeMounts } } // Map tags to string array @@ -1403,8 +1417,8 @@ async function enableNodeExecEndPoint (execData, isCLI, transaction) { if (execData.image) { const images = [ - { fogTypeId: 1, containerImage: execData.image }, - { fogTypeId: 2, containerImage: execData.image } + { archId: 1, containerImage: execData.image }, + { archId: 2, containerImage: execData.image } ] debugMicroserviceData.images = images } else { @@ -1449,8 +1463,8 @@ async function enableNodeExecEndPoint (execData, isCLI, transaction) { if (execData.image) { const images = [ - { fogTypeId: 1, containerImage: execData.image }, - { fogTypeId: 2, containerImage: execData.image } + { archId: 1, containerImage: execData.image }, + { archId: 2, containerImage: execData.image } ] await _updateImages(images, existingMicroservice.uuid, transaction) } @@ -1467,8 +1481,8 @@ async function enableNodeExecEndPoint (execData, isCLI, transaction) { if (execData.image) { const images = [ - { fogTypeId: 1, containerImage: execData.image }, - { fogTypeId: 2, containerImage: execData.image } + { archId: 1, containerImage: execData.image }, + { archId: 2, containerImage: execData.image } ] await _createMicroserviceImages(microservice, images, transaction) } From 53f19876aeec1724c3e750439a09b718901c2d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 03:43:12 +0300 Subject: [PATCH 09/75] Use application name as the sole microservice list filter Remove flowId query parameter and rename INVALID_FLOW_* errors to INVALID_APPLICATION_* across controllers and dependent services. --- src/controllers/microservices-controller.js | 10 ++------ src/helpers/constants.js | 2 +- src/helpers/error-messages.js | 4 +-- src/services/application-service.js | 12 ++++----- src/services/application-template-service.js | 2 +- src/services/microservices-service.js | 8 +++--- src/services/nats-api-service.js | 26 ++++++++++---------- src/services/nats-auth-service.js | 4 +-- src/services/router-service.js | 2 +- 9 files changed, 32 insertions(+), 38 deletions(-) diff --git a/src/controllers/microservices-controller.js b/src/controllers/microservices-controller.js index daf1659b..9d62153c 100644 --- a/src/controllers/microservices-controller.js +++ b/src/controllers/microservices-controller.js @@ -114,19 +114,13 @@ const deleteMicroserviceEndPoint = async function (req) { } const getMicroservicesByApplicationEndPoint = async function (req) { - // API Retro compatibility - const flowId = req.query.flowId - const applicationName = req.query.application - return MicroservicesService.listMicroservicesEndPoint({ applicationName, flowId }, false) + return MicroservicesService.listMicroservicesEndPoint({ applicationName }, false) } const getSystemMicroservicesByApplicationEndPoint = async function (req) { - // API Retro compatibility - const flowId = req.query.flowId - const applicationName = req.query.application - return MicroservicesService.listSystemMicroservicesEndPoint({ applicationName, flowId }, false) + return MicroservicesService.listSystemMicroservicesEndPoint({ applicationName }, false) } const createMicroservicePortMappingEndPoint = async function (req) { diff --git a/src/helpers/constants.js b/src/helpers/constants.js index 261c69f5..d3a3ad54 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -48,7 +48,7 @@ module.exports = { CMD_IOFOG_REBOOT: 'reboot', CMD_CONTROLLER: 'controller', CMD_EMAIL_ACTIVATION: 'email-activation', - CMD_FOG_TYPES: 'fog-types', + CMD_ARCHITECTURES: 'architectures', CMD_DIAGNOSTICS: 'diagnostics', CMD_STRACE_UPDATE: 'strace-update', CMD_STRACE_INFO: 'strace-info', diff --git a/src/helpers/error-messages.js b/src/helpers/error-messages.js index 48be037d..642120dd 100644 --- a/src/helpers/error-messages.js +++ b/src/helpers/error-messages.js @@ -16,8 +16,8 @@ module.exports = { DUPLICATE_NAME: 'Duplicate name \'{}\'', ALREADY_EXISTS: 'Model already exists', INVALID_CATALOG_ITEM_ID: 'Invalid catalog item id \'{}\'', - INVALID_FLOW_ID: 'Invalid application id \'{}\'', - INVALID_FLOW_NAME: 'Invalid application name \'{}\'', + INVALID_APPLICATION_ID: 'Invalid application id \'{}\'', + INVALID_APPLICATION_NAME: 'Invalid application name \'{}\'', INVALID_REGISTRY_ID: 'Invalid registry id \'{}\'', UNABLE_TO_CREATE_ACTIVATION_CODE: 'Unable to create activation code', UNABLE_TO_GET_ACTIVATION_CODE: 'Unable to create activation code', diff --git a/src/services/application-service.js b/src/services/application-service.js index 62553b73..11b4973b 100644 --- a/src/services/application-service.js +++ b/src/services/application-service.js @@ -151,7 +151,7 @@ const patchApplicationEndPoint = async function (applicationData, conditions, is const oldApplication = await ApplicationManager.findOne({ ...conditions }, transaction) if (!oldApplication) { - throw new Errors.NotFoundError(ErrorMessages.INVALID_FLOW_ID) + throw new Errors.NotFoundError(ErrorMessages.INVALID_APPLICATION_ID) } if (applicationData.name && applicationData.name !== oldApplication.name) { throw new Errors.ValidationError('Application Resource Name is immutable') @@ -217,7 +217,7 @@ const updateApplicationEndPoint = async function (applicationData, name, isCLI, const oldApplication = await ApplicationManager.findOne({ name }, transaction) if (!oldApplication) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, name)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, name)) } if (applicationData.name && applicationData.name !== oldApplication.name) { throw new Errors.ValidationError('Application Resource Name is immutable') @@ -290,7 +290,7 @@ const _updateMicroservices = async function (application, microservices, isCLI, // Update microservices const oldMicroservices = await ApplicationManager.findApplicationMicroservices({ name: application }, transaction) if (!oldMicroservices) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, application)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, application)) } const iofogUuids = [] const oldMsvcsIofogUuids = [] @@ -395,7 +395,7 @@ async function getApplication (conditions, isCLI, transaction) { const applicationRaw = await ApplicationManager.findOnePopulated(where, attributes, transaction) if (!applicationRaw) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, conditions.name || conditions.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, conditions.name || conditions.id)) } const application = await _buildApplicationObject(applicationRaw, transaction) return application @@ -409,7 +409,7 @@ async function getSystemApplication (conditions, isCLI, transaction) { const applicationRaw = await ApplicationManager.findOnePopulated(where, attributes, transaction) if (!applicationRaw) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, conditions.name || conditions.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, conditions.name || conditions.id)) } const application = await _buildApplicationObject(applicationRaw, transaction) return application @@ -440,7 +440,7 @@ const getSystemApplicationEndPoint = async function (conditions, isCLI, transact async function _updateChangeTrackingsAndDeleteMicroservicesByApplicationId (conditions, deleteMicroservices, transaction) { const microservices = await ApplicationManager.findApplicationMicroservices(conditions, transaction) if (!microservices) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, conditions.name || conditions.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, conditions.name || conditions.id)) } const iofogUuids = [] for (const ms of microservices) { diff --git a/src/services/application-template-service.js b/src/services/application-template-service.js index df68d89c..da45c2fc 100644 --- a/src/services/application-template-service.js +++ b/src/services/application-template-service.js @@ -76,7 +76,7 @@ const patchApplicationTemplateEndPoint = async function (applicationTemplateData const oldApplicationTemplate = await ApplicationTemplateManager.findOne({ ...conditions }, transaction) if (!oldApplicationTemplate) { - throw new Errors.NotFoundError(ErrorMessages.INVALID_FLOW_ID) + throw new Errors.NotFoundError(ErrorMessages.INVALID_APPLICATION_ID) } if (applicationTemplateData.name) { await _checkForDuplicateName(applicationTemplateData.name, oldApplicationTemplate.id, transaction) diff --git a/src/services/microservices-service.js b/src/services/microservices-service.js index e29de2ef..8854313c 100644 --- a/src/services/microservices-service.js +++ b/src/services/microservices-service.js @@ -2078,12 +2078,12 @@ async function _validateApplication (name, isCLI, transaction) { const application = await ApplicationManager.findOne(where, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, name)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, name)) } return application } else { // If name is not a valid integer, it's not a valid ID either - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, name)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, name)) } } return application @@ -2109,12 +2109,12 @@ async function _validateSystemApplication (name, isCLI, transaction) { const application = await ApplicationManager.findOne(where, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, name)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, name)) } return application } else { // If name is not a valid integer, it's not a valid ID either - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, name)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, name)) } } return application diff --git a/src/services/nats-api-service.js b/src/services/nats-api-service.js index db37e1a3..bc7a8af5 100644 --- a/src/services/nats-api-service.js +++ b/src/services/nats-api-service.js @@ -236,11 +236,11 @@ async function upsertHub (payload, transaction) { async function getAccount (appName, transaction) { const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } const account = await NatsAccountManager.findOne({ applicationId: application.id }, transaction) if (!account) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, application.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, application.id)) } return { id: account.id, @@ -255,7 +255,7 @@ async function getAccount (appName, transaction) { async function ensureAccount (appName, payload, transaction) { const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } if (application.natsAccess) { throw new Errors.ValidationError( @@ -303,11 +303,11 @@ async function listAllUsers (transaction) { async function listUsers (appName, transaction) { const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } const account = await NatsAccountManager.findOne({ applicationId: application.id }, transaction) if (!account) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, application.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, application.id)) } const users = await NatsUserManager.findAll({ accountId: account.id }, transaction) return { @@ -330,7 +330,7 @@ async function createUser (appName, payload, transaction) { } const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } const account = await NatsAuthService.ensureAccountForApplication(application.id, transaction) const expiresIn = payload && payload.expiresIn @@ -350,13 +350,13 @@ async function getUserCreds (appName, userName, transaction) { const sysAccount = await NatsAccountManager.findOne({ name: appName }, transaction) if (!application && (!sysAccount || (!sysAccount.isSystem && !sysAccount.isLeafSystem))) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } let accountId = null if (application) { const account = await NatsAccountManager.findOne({ applicationId: application.id }, transaction) if (!account) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, application.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, application.id)) } accountId = account.id } @@ -392,7 +392,7 @@ async function createMqttBearer (appName, payload, transaction) { } const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } const expiresIn = payload && payload.expiresIn const natsRule = payload && payload.natsRule @@ -408,11 +408,11 @@ async function createMqttBearer (appName, payload, transaction) { async function deleteUser (appName, userName, transaction) { const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } const account = await NatsAccountManager.findOne({ applicationId: application.id }, transaction) if (!account) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, application.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, application.id)) } await NatsAuthService.revokeUserByAccountAndName(account.id, userName, transaction) } @@ -420,11 +420,11 @@ async function deleteUser (appName, userName, transaction) { async function deleteMqttBearer (appName, userName, transaction) { const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } const account = await NatsAccountManager.findOne({ applicationId: application.id }, transaction) if (!account) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, application.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, application.id)) } const user = await NatsUserManager.findOne({ accountId: account.id, name: userName }, transaction) if (!user) { diff --git a/src/services/nats-auth-service.js b/src/services/nats-auth-service.js index 01a071da..515c646a 100644 --- a/src/services/nats-auth-service.js +++ b/src/services/nats-auth-service.js @@ -529,7 +529,7 @@ async function ensureAccountForApplication (applicationId, transaction) { const application = await ApplicationManager.findOne({ id: applicationId }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, applicationId)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, applicationId)) } const operator = await ensureOperator(transaction) @@ -713,7 +713,7 @@ async function createUserForAccount (accountId, userName, expiresIn, natsRuleNam await ensureDefaultRules(transaction) const account = await NatsAccountManager.findOne({ id: accountId }, transaction) if (!account) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, accountId)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, accountId)) } const existingUser = await NatsUserManager.findOne({ accountId: account.id, name: userName }, transaction) if (existingUser) { diff --git a/src/services/router-service.js b/src/services/router-service.js index 25bad1a0..c80fcbe9 100644 --- a/src/services/router-service.js +++ b/src/services/router-service.js @@ -387,7 +387,7 @@ async function _createRouterMicroservice (isEdge, uuid, microserviceConfig, tran { capAdd: 'NET_RAW' } ] if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, `system-${fog.name}`)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, `system-${fog.name}`)) } routerMicroserviceData.applicationId = application.id const routerMicroservice = await MicroserviceManager.create(routerMicroserviceData, transaction) From 61d665488666983e1117a79f4732d9da9823f3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 03:43:17 +0300 Subject: [PATCH 10/75] Rename fog-types API to architectures across public surfaces Update route, controller endpoint, CLI commands, swagger definitions, and RBAC resource paths to canonical arch and archId naming. --- docs/swagger.yaml | 146 +++++++++++++++++++-------------- src/cli/catalog.js | 6 +- src/cli/controller.js | 14 ++-- src/cli/iofog.js | 4 +- src/cli/microservice.js | 12 +-- src/config/rbac-resources.yaml | 2 +- src/controllers/controller.js | 6 +- src/routes/controller.js | 6 +- 8 files changed, 110 insertions(+), 86 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 86d383d2..db43dcfa 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -18,12 +18,12 @@ paths: $ref: "#/components/schemas/ServiceStatusResponse" "500": description: Internal Server Error - /fog-types: + /architectures: get: tags: - Controller - summary: Gets ioFog types list - operationId: getIOFogTypes + summary: Gets architecture list + operationId: getArchitectures responses: "200": description: Success @@ -35,7 +35,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/IOFogTypesResponse" + $ref: "#/components/schemas/ArchitecturesResponse" "500": description: Internal Server Error /iofog-list: @@ -1741,13 +1741,6 @@ paths: summary: Gets list of microservices operationId: getMicroservicesList parameters: - - in: query - name: flowId - deprecated: true - description: Flow Id - required: false - schema: - type: integer - in: query name: application description: Application name @@ -1946,13 +1939,6 @@ paths: security: - authToken: [] parameters: - - in: query - name: flowId - deprecated: true - description: Flow Id - required: false - schema: - type: integer - in: query name: application description: Application name @@ -6785,7 +6771,7 @@ components: properties: containerImage: type: string - fogTypeId: + archId: type: string iofogUuid: type: string @@ -6913,7 +6899,7 @@ components: properties: containerImage: type: string - fogTypeId: + archId: type: string iofogUuid: type: string @@ -7017,14 +7003,14 @@ components: example: ok timestamp: type: number - IOFogTypesResponse: + ArchitecturesResponse: type: object properties: - fogTypes: + architectures: type: array items: - $ref: "#/components/schemas/IOFogType" - IOFogType: + $ref: "#/components/schemas/Architecture" + Architecture: type: object properties: id: @@ -7068,8 +7054,6 @@ components: type: number lastStatusTime: type: number - processedMessages: - type: number lastCommandTime: type: number logFileCount: @@ -7112,16 +7096,16 @@ components: type: string ipAddressExternal: type: string - catalogItemMessageCounts: - type: number - messageSpeed: - type: number networkInterface: type: string - dockerUrl: + containerEngineUrl: type: string containerEngine: type: string + enum: + - edgelet + - docker + - podman deploymentType: type: string diskLimit: @@ -7156,7 +7140,7 @@ components: type: string watchdogEnabled: type: boolean - dockerPruningFrequency: + pruningFrequency: type: number availableDiskThreshold: type: number @@ -7170,8 +7154,14 @@ components: type: number updatedAt: type: number - fogTypeId: + archId: type: number + arch: + $ref: "#/components/schemas/Architecture" + availableRuntimes: + type: array + items: + type: string routerMode: type: string enum: @@ -7223,11 +7213,15 @@ components: type: number description: type: string - dockerUrl: + containerEngineUrl: type: string default: unix:///var/run/docker.sock containerEngine: type: string + enum: + - edgelet + - docker + - podman default: docker deploymentType: type: string @@ -7277,9 +7271,11 @@ components: abstractedHardwareEnabled: type: boolean default: false - fogType: + archId: type: number - dockerPruningFrequency: + minimum: 0 + maximum: 4 + pruningFrequency: type: number availableDiskThreshold: type: number @@ -7359,13 +7355,22 @@ components: properties: type: type: number - enum: - - 1 - - 2 + minimum: 0 + maximum: 4 description: | - Architecture - * '1': x86 - * '2': arm + Architecture code + * '0': auto + * '1': amd64 + * '2': arm64 + * '3': riscv64 + * '4': arm + engine: + type: string + enum: + - edgelet + - docker + - podman + description: Container engine selected at provision time key: type: string description: provisioning key @@ -7417,10 +7422,14 @@ components: properties: networkInterface: type: string - dockerUrl: + containerEngineUrl: type: string containerEngine: type: string + enum: + - edgelet + - docker + - podman deploymentType: type: string diskLimit: @@ -7453,7 +7462,7 @@ components: type: string availableDiskThreshold: type: number - dockerPruningFrequency: + pruningFrequency: type: number routerHost: type: string @@ -7464,10 +7473,14 @@ components: properties: networkInterface: type: string - dockerUrl: + containerEngineUrl: type: string containerEngine: type: string + enum: + - edgelet + - docker + - podman deploymentType: type: string diskLimit: @@ -7502,7 +7515,7 @@ components: type: string availableDiskThreshold: type: number - dockerPruningFrequency: + pruningFrequency: type: number IOFogNodeGpsRequest: type: object @@ -7516,6 +7529,8 @@ components: properties: daemonStatus: type: string + warningMessage: + type: string daemonOperatingDuration: type: number daemonLastStart: @@ -7538,6 +7553,10 @@ components: type: integer systemTotalCpu: type: number + securityStatus: + type: string + securityViolationInfo: + type: string microserviceStatus: type: string repositoryCount: @@ -7550,14 +7569,18 @@ components: type: number ipAddress: type: string - processedMessages: - type: number - microserviceMessageCounts: + ipAddressExternal: type: string - messageSpeed: - type: number lastCommandTime: type: number + availableRuntimes: + type: array + items: + type: string + runtimeAgentPhase: + type: string + controlPlaneQuiesced: + type: boolean tunnelStatus: type: string version: @@ -7566,6 +7589,12 @@ components: type: boolean isReadyToRollback: type: boolean + activeVolumeMounts: + type: number + volumeMountLastUpdate: + type: number + gpsStatus: + type: string IOFogNodeTunnelConfigResponse: type: object properties: @@ -7919,7 +7948,7 @@ components: properties: containerImage: type: string - fogTypeId: + archId: type: number enum: - 1 @@ -8010,7 +8039,7 @@ components: properties: containerImage: type: string - fogTypeId: + archId: type: string iofogUuid: type: string @@ -8089,15 +8118,10 @@ components: properties: containerImage: type: string - fogTypeId: + archId: type: string - flowId: - type: integer - deprecated: true application: - oneOf: - - type: string - - type: integer + type: string iofogUuid: type: string agentName: @@ -8177,7 +8201,7 @@ components: properties: containerImage: type: string - fogTypeId: + archId: type: string runAsUser: type: string diff --git a/src/cli/catalog.js b/src/cli/catalog.js index 9f15b451..dc2473f4 100644 --- a/src/cli/catalog.js +++ b/src/cli/catalog.js @@ -28,7 +28,7 @@ const JSON_SCHEMA = AppHelper.stringifyCliJsonSchema({ images: [ { containerImage: 'string', - fogTypeId: 1 + archId: 1 } ], publisher: 'string', @@ -320,7 +320,7 @@ const _createCatalogItemObject = function (catalogItem) { catalogItemObj.images.push( { containerImage: catalogItem.x86Image, - fogTypeId: 1 + archId: 1 } ) } @@ -328,7 +328,7 @@ const _createCatalogItemObject = function (catalogItem) { catalogItemObj.images.push( { containerImage: catalogItem.armImage, - fogTypeId: 2 + archId: 2 } ) } diff --git a/src/cli/controller.js b/src/cli/controller.js index 9ae8561c..7e4e003b 100644 --- a/src/cli/controller.js +++ b/src/cli/controller.js @@ -25,13 +25,13 @@ class Controller extends BaseCLIHandler { { name: 'command', defaultOption: true, - description: 'status, fog-types, version', + description: 'status, architectures, version', group: constants.CMD } ] this.commands = { [constants.CMD_STATUS]: 'Display iofog-controller service status.', - [constants.CMD_FOG_TYPES]: 'List all Fog-types.', + [constants.CMD_ARCHITECTURES]: 'List all architectures.', [constants.CMD_VERSION]: 'Display iofog-controller service version.' } } @@ -48,8 +48,8 @@ class Controller extends BaseCLIHandler { case constants.CMD_STATUS: await _executeCase(controllerCommand, constants.CMD_STATUS, _getStatus) break - case constants.CMD_FOG_TYPES: - await _executeCase(controllerCommand, constants.CMD_FOG_TYPES, _getFogTypes) + case constants.CMD_ARCHITECTURES: + await _executeCase(controllerCommand, constants.CMD_ARCHITECTURES, _getArchitectures) break case constants.CMD_VERSION: await _executeCase(controllerCommand, constants.CMD_VERSION, _getVersion) @@ -78,9 +78,9 @@ const _getStatus = async function () { logger.cliRes(JSON.stringify(response, null, 2)) } -const _getFogTypes = async function () { - logger.cliReq('controller fog-types') - const response = await ControllerService.getFogTypes(true) +const _getArchitectures = async function () { + logger.cliReq('controller architectures') + const response = await ControllerService.getArchitectures(true) logger.cliRes(JSON.stringify(response, null, 2)) } diff --git a/src/cli/iofog.js b/src/cli/iofog.js index 8ea90aae..123df924 100644 --- a/src/cli/iofog.js +++ b/src/cli/iofog.js @@ -39,7 +39,7 @@ const JSON_SCHEMA = AppHelper.stringifyCliJsonSchema({ bluetoothEnabled: false, watchdogEnabled: true, abstractedHardwareEnabled: false, - fogType: 0, + archId: 0, dockerPruningFrequency: 0, availableDiskThreshold: 0, logLevel: 'string', @@ -492,7 +492,7 @@ function _createFogObject (cliData) { watchdogEnabled: AppHelper.validateBooleanCliOptions(cliData.watchdogEnable, cliData.watchdogDisable), abstractedHardwareEnabled: AppHelper.validateBooleanCliOptions(cliData.absHwEnable, cliData.absHwDisable), - fogType: cliData.fogType, + archId: cliData.archId, dockerPruningFrequency: cliData.dockerPruningFrequency, availableDiskThreshold: cliData.availableDiskThreshold, logLevel: cliData.logLevel, diff --git a/src/cli/microservice.js b/src/cli/microservice.js index 59a7002b..d4cde52b 100644 --- a/src/cli/microservice.js +++ b/src/cli/microservice.js @@ -29,7 +29,7 @@ const JSON_SCHEMA_ADD = AppHelper.stringifyCliJsonSchema( images: [ { containerImage: 'string', - fogTypeId: 1 + archId: 1 } ], registryId: 1, @@ -91,7 +91,7 @@ const JSON_SCHEMA_UPDATE = AppHelper.stringifyCliJsonSchema( images: [ { containerImage: 'string', - fogTypeId: 1 + archId: 1 } ], registryId: 1, @@ -622,7 +622,7 @@ const _updateMicroserviceObject = function (obj) { images.push( { containerImage: obj.x86Image, - fogTypeId: 1 + archId: 1 } ) } @@ -630,7 +630,7 @@ const _updateMicroserviceObject = function (obj) { images.push( { containerImage: obj.armImage, - fogTypeId: 2 + archId: 2 } ) } @@ -686,7 +686,7 @@ const _createMicroserviceObject = function (obj) { microserviceObj.images.push( { containerImage: obj.x86Image, - fogTypeId: 1 + archId: 1 } ) } @@ -694,7 +694,7 @@ const _createMicroserviceObject = function (obj) { microserviceObj.images.push( { containerImage: obj.armImage, - fogTypeId: 2 + archId: 2 } ) } diff --git a/src/config/rbac-resources.yaml b/src/config/rbac-resources.yaml index bd669621..19116999 100644 --- a/src/config/rbac-resources.yaml +++ b/src/config/rbac-resources.yaml @@ -769,7 +769,7 @@ resources: - path: /api/v3/status methods: GET: [] - - path: /api/v3/fog-types/ + - path: /api/v3/architectures/ methods: GET: [] diff --git a/src/controllers/controller.js b/src/controllers/controller.js index abd4650e..35da1ca1 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -17,11 +17,11 @@ const statusControllerEndPoint = async function (req) { return ControllerService.statusController(false) } -const fogTypesEndPoint = async function (req) { - return ControllerService.getFogTypes(false) +const architecturesEndPoint = async function (req) { + return ControllerService.getArchitectures(false) } module.exports = { statusControllerEndPoint: statusControllerEndPoint, - fogTypesEndPoint: fogTypesEndPoint + architecturesEndPoint: architecturesEndPoint } diff --git a/src/routes/controller.js b/src/routes/controller.js index 36e9fb8f..0c6bd7fe 100644 --- a/src/routes/controller.js +++ b/src/routes/controller.js @@ -36,14 +36,14 @@ module.exports = [ }, { method: 'get', - path: '/api/v3/fog-types/', + path: '/api/v3/architectures/', middleware: async (req, res) => { logger.apiReq(req) const successCode = constants.HTTP_CODE_SUCCESS const errorCodes = [] - const fogTypesEndPoint = ResponseDecorator.handleErrors(Controller.fogTypesEndPoint, successCode, errorCodes) - const responseObject = await fogTypesEndPoint(req) + const architecturesEndPoint = ResponseDecorator.handleErrors(Controller.architecturesEndPoint, successCode, errorCodes) + const responseObject = await architecturesEndPoint(req) res .status(responseObject.code) From cfa7bde69972305a473d4d65eedf16a8f3e6958f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 03:43:21 +0300 Subject: [PATCH 11/75] Update tests for Edgelet agent contract and architectures API Add Edgelet-shaped status fixtures, architectures endpoint coverage, and align CLI test field lists with archId and containerEngineUrl. --- scripts/cli-tests.js | 6 +- test/src/controllers/agent-controller.test.js | 24 +- .../controllers/controller-controller.test.js | 47 +--- test/src/controllers/iofog-controller.test.js | 66 ++--- test/src/services/agent-service.test.js | 266 +++++++++++++----- test/src/services/controller-service.test.js | 37 +-- test/src/services/iofog-service.test.js | 121 ++++---- 7 files changed, 309 insertions(+), 258 deletions(-) diff --git a/scripts/cli-tests.js b/scripts/cli-tests.js index e64f86f3..564a5946 100644 --- a/scripts/cli-tests.js +++ b/scripts/cli-tests.js @@ -34,7 +34,7 @@ let testsCounter = 0 let testsFailed = 0 const controllerStatusFields = ['status', 'timestamp'] -const controllerFogTypesFields = ['fogTypes'] +const controllerArchitecturesFields = ['architectures'] const ioFogCreateFields = ['uuid'] const ioFogListFields = ['fogs'] @@ -62,7 +62,7 @@ async function seedTestData () { console.log('\nCreating system fog') await FogService.createFogEndPoint({ name: 'default-router', - fogType: 1, + archId: 1, isSystem: true, routerMode: 'interior', messagingPort: 5671, @@ -80,7 +80,7 @@ function testControllerSection () { console.log('\n=============================\nStarting controller section..') responseHasFields(testCommand('controller status'), controllerStatusFields) - responseHasFields(testCommand('controller fog-types'), controllerFogTypesFields) + responseHasFields(testCommand('controller architectures'), controllerArchitecturesFields) hasSomeResponse(testCommand('controller version')) } diff --git a/test/src/controllers/agent-controller.test.js b/test/src/controllers/agent-controller.test.js index 9f477637..549b6eec 100644 --- a/test/src/controllers/agent-controller.test.js +++ b/test/src/controllers/agent-controller.test.js @@ -129,7 +129,7 @@ describe('Agent Controller', () => { def('fog', () => 'fog!') def('networkInterface', () => 'testNetworkInterface') - def('dockerUrl', () => 'testDockerUrl') + def('containerEngineUrl', () => 'testContainerEngineUrl') def('diskLimit', 15) def('diskDirectory', () => 'testDiskDirectory') def('memoryLimit', () => 25) @@ -148,7 +148,7 @@ describe('Agent Controller', () => { def('req', () => ({ body: { networkInterface: $networkInterface, - dockerUrl: $dockerUrl, + containerEngineUrl: $containerEngineUrl, diskLimit: $diskLimit, diskDirectory: $diskDirectory, memoryLimit: $memoryLimit, @@ -177,7 +177,7 @@ describe('Agent Controller', () => { await $subject expect(AgentService.updateAgentConfig).to.have.been.calledWith({ networkInterface: $networkInterface, - dockerUrl: $dockerUrl, + containerEngineUrl: $containerEngineUrl, diskLimit: $diskLimit, diskDirectory: $diskDirectory, memoryLimit: $memoryLimit, @@ -266,9 +266,9 @@ describe('Agent Controller', () => { def('lastStatusTime', () => 15555555) def('ipAddress', () => 'testIpAddress') def('ipAddressExternal', () => 'testIpAddressExternal') - def('processedMessages', () => 155) - def('microserviceMessageCounts', () => 1555) - def('messageSpeed', () => 5.00) + def('availableRuntimes', () => ['edgelet']) + def('runtimeAgentPhase', () => 'Running') + def('controlPlaneQuiesced', () => false) def('lastCommandTime', () => 155555555) def('tunnelStatus', () => 'testTunnelStatus') def('version', () => '1.5.6') @@ -293,9 +293,9 @@ describe('Agent Controller', () => { lastStatusTime: $lastStatusTime, ipAddress: $ipAddress, ipAddressExternal: $ipAddressExternal, - processedMessages: $processedMessages, - microserviceMessageCounts: $microserviceMessageCounts, - messageSpeed: $messageSpeed, + availableRuntimes: $availableRuntimes, + runtimeAgentPhase: $runtimeAgentPhase, + controlPlaneQuiesced: $controlPlaneQuiesced, lastCommandTime: $lastCommandTime, tunnelStatus: $tunnelStatus, version: $version, @@ -329,9 +329,9 @@ describe('Agent Controller', () => { lastStatusTime: $lastStatusTime, ipAddress: $ipAddress, ipAddressExternal: $ipAddressExternal, - processedMessages: $processedMessages, - microserviceMessageCounts: $microserviceMessageCounts, - messageSpeed: $messageSpeed, + availableRuntimes: $availableRuntimes, + runtimeAgentPhase: $runtimeAgentPhase, + controlPlaneQuiesced: $controlPlaneQuiesced, lastCommandTime: $lastCommandTime, tunnelStatus: $tunnelStatus, version: $version, diff --git a/test/src/controllers/controller-controller.test.js b/test/src/controllers/controller-controller.test.js index fa4f664c..e2037a77 100644 --- a/test/src/controllers/controller-controller.test.js +++ b/test/src/controllers/controller-controller.test.js @@ -44,57 +44,24 @@ describe('Controller', () => { }) }) - describe('.emailActivationEndPoint()', () => { - def('req', () => ({ - body: {}, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.emailActivationEndPoint($req, $user)) - - beforeEach(() => { - $sandbox.stub(ControllerService, 'emailActivation').returns($response) - }) - - it('calls ControllerService.emailActivation with correct args', async () => { - await $subject - expect(ControllerService.emailActivation).to.have.been.calledWith(false) - }) - - context('when ControllerService#emailActivation fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ControllerService#emailActivation succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('.fogTypesEndPoint()', () => { + describe('.architecturesEndPoint()', () => { def('req', () => ({ body: {}, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.fogTypesEndPoint($req, $user)) + def('subject', () => $subject.architecturesEndPoint($req)) beforeEach(() => { - $sandbox.stub(ControllerService, 'getFogTypes').returns($response) + $sandbox.stub(ControllerService, 'getArchitectures').returns($response) }) - it('calls ControllerService.getFogTypes with correct args', async () => { + it('calls ControllerService.getArchitectures with correct args', async () => { await $subject - expect(ControllerService.getFogTypes).to.have.been.calledWith(false) + expect(ControllerService.getArchitectures).to.have.been.calledWith(false) }) - context('when ControllerService#getFogTypes fails', () => { + context('when ControllerService#getArchitectures fails', () => { const error = 'Error!' def('response', () => Promise.reject(error)) @@ -104,7 +71,7 @@ describe('Controller', () => { }) }) - context('when ControllerService#getFogTypes succeeds', () => { + context('when ControllerService#getArchitectures succeeds', () => { it(`succeeds`, () => { return expect($subject).to.eventually.equal(undefined) }) diff --git a/test/src/controllers/iofog-controller.test.js b/test/src/controllers/iofog-controller.test.js index 1e8ad85d..7172abe5 100644 --- a/test/src/controllers/iofog-controller.test.js +++ b/test/src/controllers/iofog-controller.test.js @@ -20,7 +20,7 @@ describe('ioFog Controller', () => { def('latitude', () => 15) def('longitude', () => 16) def('description', () => 'testDescription') - def('dockerUrl', () => 'testDockerUrl') + def('containerEngineUrl', () => 'testContainerEngineUrl') def('diskLimit', () => 25) def('diskDirectory', () => 'testDiskDirectory') def('memoryLimit', () => 35) @@ -34,7 +34,7 @@ describe('ioFog Controller', () => { def('bluetoothEnabled', () => false) def('watchdogEnabled', () => true) def('abstractedHardwareEnabled', () => false) - def('fogType', () => 0) + def('archId', () => 0) def('req', () => ({ body: { @@ -43,7 +43,7 @@ describe('ioFog Controller', () => { latitude: $latitude, longitude: $longitude, description: $description, - dockerUrl: $dockerUrl, + containerEngineUrl: $containerEngineUrl, diskLimit: $diskLimit, diskDirectory: $diskDirectory, memoryLimit: $memoryLimit, @@ -57,12 +57,12 @@ describe('ioFog Controller', () => { bluetoothEnabled: $bluetoothEnabled, watchdogEnabled: $watchdogEnabled, abstractedHardwareEnabled: $abstractedHardwareEnabled, - fogType: $fogType, + archId: $archId, }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.createFogEndPoint($req, $user)) + def('subject', () => $subject.createFogEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'createFogEndPoint').returns($response) @@ -76,7 +76,7 @@ describe('ioFog Controller', () => { latitude: $latitude, longitude: $longitude, description: $description, - dockerUrl: $dockerUrl, + containerEngineUrl: $containerEngineUrl, diskLimit: $diskLimit, diskDirectory: $diskDirectory, memoryLimit: $memoryLimit, @@ -90,8 +90,8 @@ describe('ioFog Controller', () => { bluetoothEnabled: $bluetoothEnabled, watchdogEnabled: $watchdogEnabled, abstractedHardwareEnabled: $abstractedHardwareEnabled, - fogType: $fogType, - }, $user, false) + archId: $archId, + }, false) }) context('when ioFogService#createFogEndPoint fails', () => { @@ -120,7 +120,7 @@ describe('ioFog Controller', () => { def('latitude', () => 15) def('longitude', () => 16) def('description', () => 'testDescription') - def('dockerUrl', () => 'testDockerUrl') + def('containerEngineUrl', () => 'testContainerEngineUrl') def('diskLimit', () => 25) def('diskDirectory', () => 'testDiskDirectory') def('memoryLimit', () => 35) @@ -134,7 +134,7 @@ describe('ioFog Controller', () => { def('bluetoothEnabled', () => false) def('watchdogEnabled', () => true) def('abstractedHardwareEnabled', () => false) - def('fogType', () => 0) + def('archId', () => 0) def('req', () => ({ params: { @@ -146,7 +146,7 @@ describe('ioFog Controller', () => { latitude: $latitude, longitude: $longitude, description: $description, - dockerUrl: $dockerUrl, + containerEngineUrl: $containerEngineUrl, diskLimit: $diskLimit, diskDirectory: $diskDirectory, memoryLimit: $memoryLimit, @@ -160,11 +160,11 @@ describe('ioFog Controller', () => { bluetoothEnabled: $bluetoothEnabled, watchdogEnabled: $watchdogEnabled, abstractedHardwareEnabled: $abstractedHardwareEnabled, - fogType: $fogType, + archId: $archId, }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.updateFogEndPoint($req, $user)) + def('subject', () => $subject.updateFogEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'updateFogEndPoint').returns($response) @@ -179,7 +179,7 @@ describe('ioFog Controller', () => { latitude: $latitude, longitude: $longitude, description: $description, - dockerUrl: $dockerUrl, + containerEngineUrl: $containerEngineUrl, diskLimit: $diskLimit, diskDirectory: $diskDirectory, memoryLimit: $memoryLimit, @@ -193,8 +193,8 @@ describe('ioFog Controller', () => { bluetoothEnabled: $bluetoothEnabled, watchdogEnabled: $watchdogEnabled, abstractedHardwareEnabled: $abstractedHardwareEnabled, - fogType: $fogType, - }, $user, false) + archId: $archId, + }, false) }) context('when ioFogService#updateFogEndPoint fails', () => { @@ -225,7 +225,7 @@ describe('ioFog Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteFogEndPoint($req, $user)) + def('subject', () => $subject.deleteFogEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'deleteFogEndPoint').returns($response) @@ -233,7 +233,7 @@ describe('ioFog Controller', () => { it('calls ioFogService.deleteFogEndPoint with correct args', async () => { await $subject - expect(ioFogService.deleteFogEndPoint).to.have.been.calledWith({ uuid: $uuid }, $user, false) + expect(ioFogService.deleteFogEndPoint).to.have.been.calledWith({ uuid: $uuid }, false) }) context('when ioFogService#deleteFogEndPoint fails', () => { @@ -263,7 +263,7 @@ describe('ioFog Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getFogEndPoint($req, $user)) + def('subject', () => $subject.getFogEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'getFogEndPoint').returns($response) @@ -271,7 +271,7 @@ describe('ioFog Controller', () => { it('calls ioFogService.getFogEndPoint with correct args', async () => { await $subject - expect(ioFogService.getFogEndPoint).to.have.been.calledWith({ uuid: $uuid }, $user, false) + expect(ioFogService.getFogEndPoint).to.have.been.calledWith({ uuid: $uuid }, false) }) context('when ioFogService#getFogEndPoint fails', () => { @@ -304,7 +304,7 @@ describe('ioFog Controller', () => { def('queryParseResponse', () => ({ filters: $filters, })) - def('subject', () => $subject.getFogListEndPoint($req, $user)) + def('subject', () => $subject.getFogListEndPoint($req)) beforeEach(() => { $sandbox.stub(qs, 'parse').returns($queryParseResponse) @@ -313,7 +313,7 @@ describe('ioFog Controller', () => { it('calls qs.parse with correct args', async () => { await $subject - expect(qs.parse).to.have.been.calledWith($queryParseResponse) + expect(qs.parse).to.have.been.calledWith($req.query) }) context('when qs.parse fails', () => { @@ -329,7 +329,7 @@ describe('ioFog Controller', () => { context('when qs.parse succeeds', () => { it('calls ioFogService.getFogListEndPoint with correct args', async () => { await $subject - expect(ioFogService.getFogListEndPoint).to.have.been.calledWith($filters, $user, false) + expect(ioFogService.getFogListEndPoint).to.have.been.calledWith($filters, false) }) context('when ioFogService.getFogListEndPoint fails', () => { @@ -360,7 +360,7 @@ describe('ioFog Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.generateProvisioningKeyEndPoint($req, $user)) + def('subject', () => $subject.generateProvisioningKeyEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'generateProvisioningKeyEndPoint').returns($response) @@ -368,7 +368,7 @@ describe('ioFog Controller', () => { it('calls ioFogService.generateProvisioningKeyEndPoint with correct args', async () => { await $subject - expect(ioFogService.generateProvisioningKeyEndPoint).to.have.been.calledWith({ uuid: $uuid }, $user, false) + expect(ioFogService.generateProvisioningKeyEndPoint).to.have.been.calledWith({ uuid: $uuid }, false) }) context('when ioFogService#generateProvisioningKeyEndPoint fails', () => { @@ -400,7 +400,7 @@ describe('ioFog Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.setFogVersionCommandEndPoint($req, $user)) + def('subject', () => $subject.setFogVersionCommandEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'setFogVersionCommandEndPoint').returns($response) @@ -411,7 +411,7 @@ describe('ioFog Controller', () => { expect(ioFogService.setFogVersionCommandEndPoint).to.have.been.calledWith({ uuid: $uuid, versionCommand: $versionCommand, - }, $user, false) + }, false) }) context('when ioFogService#setFogVersionCommandEndPoint fails', () => { @@ -441,7 +441,7 @@ describe('ioFog Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.setFogRebootCommandEndPoint($req, $user)) + def('subject', () => $subject.setFogRebootCommandEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'setFogRebootCommandEndPoint').returns($response) @@ -449,7 +449,7 @@ describe('ioFog Controller', () => { it('calls ioFogService.setFogRebootCommandEndPoint with correct args', async () => { await $subject - expect(ioFogService.setFogRebootCommandEndPoint).to.have.been.calledWith({ uuid: $uuid }, $user, false) + expect(ioFogService.setFogRebootCommandEndPoint).to.have.been.calledWith({ uuid: $uuid }, false) }) context('when ioFogService#setFogRebootCommandEndPoint fails', () => { @@ -479,7 +479,7 @@ describe('ioFog Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getHalHardwareInfoEndPoint($req, $user)) + def('subject', () => $subject.getHalHardwareInfoEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'getHalHardwareInfoEndPoint').returns($response) @@ -487,7 +487,7 @@ describe('ioFog Controller', () => { it('calls ioFogService.getHalHardwareInfoEndPoint with correct args', async () => { await $subject - expect(ioFogService.getHalHardwareInfoEndPoint).to.have.been.calledWith({ uuid: $uuid }, $user, false) + expect(ioFogService.getHalHardwareInfoEndPoint).to.have.been.calledWith({ uuid: $uuid }, false) }) context('when ioFogService#getHalHardwareInfoEndPoint fails', () => { @@ -517,7 +517,7 @@ describe('ioFog Controller', () => { }, })) def('response', () => Promise.resolve({ info: undefined })) - def('subject', () => $subject.getHalUsbInfoEndPoint($req, $user)) + def('subject', () => $subject.getHalUsbInfoEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'getHalUsbInfoEndPoint').returns($response) @@ -525,7 +525,7 @@ describe('ioFog Controller', () => { it('calls ioFogService.getHalUsbInfoEndPoint with correct args', async () => { await $subject - expect(ioFogService.getHalUsbInfoEndPoint).to.have.been.calledWith({ uuid: $uuid }, $user, false) + expect(ioFogService.getHalUsbInfoEndPoint).to.have.been.calledWith({ uuid: $uuid }, false) }) context('when ioFogService#getHalUsbInfoEndPoint fails', () => { diff --git a/test/src/services/agent-service.test.js b/test/src/services/agent-service.test.js index 35c0f968..2b362c7c 100644 --- a/test/src/services/agent-service.test.js +++ b/test/src/services/agent-service.test.js @@ -10,6 +10,8 @@ const FogKeyService = require('../../../src/services/iofog-key-service') const AppHelper = require('../../../src/helpers/app-helper') const ChangeTrackingService = require('../../../src/services/change-tracking-service') const MicroserviceStatusManager = require('../../../src/data/managers/microservice-status-manager') +const MicroserviceExecStatusManager = require('../../../src/data/managers/microservice-exec-status-manager') +const ApplicationManager = require('../../../src/data/managers/application-manager') const MicroserviceService = require('../../../src/services/microservices-service') const RegistryManager = require('../../../src/data/managers/registry-manager') const TunnelManager = require('../../../src/data/managers/tunnel-manager') @@ -21,7 +23,7 @@ const USBInfoManager = require('../../../src/data/managers/usb-info-manager') const Sequelize = require('sequelize') const Op = Sequelize.Op const path = require('path') -const MicroserviceStates = require('../../../src/enums/microservice-state') +const { microserviceState } = require('../../../src/enums/microservice-state') const FogStates = require('../../../src/enums/fog-state') const constants = require('../../../src/helpers/constants') @@ -185,7 +187,7 @@ describe('Agent Service', () => { expect(ioFogManager.update).to.have.been.calledWith({ uuid: $uuid, }, { - fogTypeId: provisionData.type, + archId: provisionData.type, }, transaction) }) @@ -233,14 +235,64 @@ describe('Agent Service', () => { }) }) + describe('.agentProvision() with engine', () => { + const transaction = {} + const provisionDataWithEngine = { + type: 1, + key: 'dpodkqwdpj', + engine: 'edgelet', + } + + def('uuid', () => 'testUuid') + def('subject', () => AgentService.agentProvision(provisionDataWithEngine, transaction)) + def('validatorResponse', () => Promise.resolve(true)) + def('fogProvisionKeyManagerResponse', () => Promise.resolve({ + iofogUuid: $uuid, + expirationTime: new Date(Date.now() + 3600000), + })) + def('iofogManagerResponse', () => Promise.resolve({ uuid: $uuid })) + def('microserviceManagerResponse', () => Promise.resolve()) + def('fogKeyServiceGenerateResponse', () => Promise.resolve({ + publicKey: 'testPublicKey', + privateKey: 'testPrivateKey', + })) + def('fogKeyServiceStoreResponse', () => Promise.resolve()) + def('iofogManagerUpdateResponse', () => Promise.resolve()) + def('fogProvisionKeyManagerDeleteResponse', () => Promise.resolve()) + def('changeTrackingUpdateResponse', () => Promise.resolve()) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').returns($validatorResponse) + $sandbox.stub(FogProvisionKeyManager, 'findOne').returns($fogProvisionKeyManagerResponse) + $sandbox.stub(MicroserviceManager, 'findAllWithDependencies').returns($microserviceManagerResponse) + $sandbox.stub(ioFogManager, 'findOne').returns($iofogManagerResponse) + $sandbox.stub(FogKeyService, 'generateKeyPair').returns($fogKeyServiceGenerateResponse) + $sandbox.stub(FogKeyService, 'storePublicKey').returns($fogKeyServiceStoreResponse) + $sandbox.stub(ioFogManager, 'update').returns($iofogManagerUpdateResponse) + $sandbox.stub(FogProvisionKeyManager, 'delete').returns($fogProvisionKeyManagerDeleteResponse) + $sandbox.stub(ChangeTrackingService, 'update').returns($changeTrackingUpdateResponse) + }) + + it('persists containerEngine from engine field', async () => { + await $subject + expect(ioFogManager.update).to.have.been.calledWith({ + uuid: $uuid, + }, { + archId: provisionDataWithEngine.type, + containerEngine: 'edgelet', + }, transaction) + }) + }) describe('.agentDeprovision()', () => { const deprovisionData = { microserviceUuids: ['uuid'] } - const fogManagerUpdateData = { daemonStatus: FogStates.UNKNOWN, ipAddress: '0.0.0.0', ipAddressExternal: '0.0.0.0' } + const fogManagerUpdateData = { daemonStatus: FogStates.DEPROVISIONED, ipAddress: '0.0.0.0', ipAddressExternal: '0.0.0.0' } const transaction = {} const error = 'Error!' + def('uuid', () => 'testUuid') + def('fog', () => ({ uuid: $uuid, })) @@ -251,11 +303,14 @@ describe('Agent Service', () => { def('validatorResponse', () => Promise.resolve(true)) def('microserviceStatusUpdateResponse', () => Promise.resolve()) + def('microserviceExecStatusUpdateResponse', () => Promise.resolve()) def('iofogManagerUpdateResponse', () => Promise.resolve()) beforeEach(() => { $sandbox.stub(Validator, 'validate').returns($validatorResponse) $sandbox.stub(MicroserviceStatusManager, 'update').returns($microserviceStatusUpdateResponse) + $sandbox.stub(MicroserviceExecStatusManager, 'update').returns($microserviceExecStatusUpdateResponse) + $sandbox.stub(FogKeyService, 'deletePublicKey').returns(Promise.resolve()) $sandbox.stub(ioFogManager, 'update').returns($iofogManagerUpdateResponse) }) @@ -277,7 +332,7 @@ describe('Agent Service', () => { await $subject expect(MicroserviceStatusManager.update).to.have.been.calledWith( { microserviceUuid: deprovisionData.microserviceUuids }, - { status: MicroserviceStates.DELETING }, + { status: microserviceState.DELETING }, transaction ) }) @@ -323,7 +378,7 @@ describe('Agent Service', () => { describe('.updateAgentConfig()', () => { const agentConfig = { networkInterface: 'testNetworkInterface', - dockerUrl: 'testDockerUrl', + containerEngineUrl: 'testContainerEngineUrl', diskLimit: 5, diskDirectory: 'testDiskDirectory', memoryLimit: 15, @@ -338,7 +393,7 @@ describe('Agent Service', () => { latitude: 35, longitude: 36, gpsMode: 'testGpsMode', - dockerPruningFrequency: 10, + pruningFrequency: 10, availableDiskThreshold: 20, logLevel: 'INFO', timeZone: 'America/Los_Angeles' @@ -359,7 +414,7 @@ describe('Agent Service', () => { def('subject', () => $subject.updateAgentConfig(agentConfig, $fog, transaction)) def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse', () => agentConfig) + def('deleteUndefinedFieldsResponse', () => expectedFogUpdate) def('iofogManagerUpdateResponse', () => Promise.resolve()) beforeEach(() => { @@ -381,10 +436,36 @@ describe('Agent Service', () => { }) }) + const expectedFogUpdate = { + networkInterface: agentConfig.networkInterface, + dockerUrl: agentConfig.containerEngineUrl, + diskLimit: agentConfig.diskLimit, + diskDirectory: agentConfig.diskDirectory, + memoryLimit: agentConfig.memoryLimit, + cpuLimit: agentConfig.cpuLimit, + logLimit: agentConfig.logLimit, + logDirectory: agentConfig.logDirectory, + logFileCount: agentConfig.logFileCount, + statusFrequency: agentConfig.statusFrequency, + changeFrequency: agentConfig.changeFrequency, + deviceScanFrequency: agentConfig.deviceScanFrequency, + watchdogEnabled: agentConfig.watchdogEnabled, + latitude: agentConfig.latitude, + longitude: agentConfig.longitude, + gpsMode: agentConfig.gpsMode, + gpsDevice: agentConfig.gpsDevice, + gpsScanFrequency: agentConfig.gpsScanFrequency, + edgeGuardFrequency: agentConfig.edgeGuardFrequency, + dockerPruningFrequency: agentConfig.pruningFrequency, + availableDiskThreshold: agentConfig.availableDiskThreshold, + logLevel: agentConfig.logLevel, + timeZone: agentConfig.timeZone + } + context('when Validator#validate() succeeds', () => { it('calls AppHelper.deleteUndefinedFields with correct args', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(agentConfig) + expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(expectedFogUpdate) }) context('when AppHelper#deleteUndefinedFields fails', () => { @@ -402,7 +483,7 @@ describe('Agent Service', () => { await $subject expect(ioFogManager.update).to.have.been.calledWith({ uuid: $uuid, - }, agentConfig, transaction) + }, expectedFogUpdate, transaction) }) context('when ioFogManager#update fails', () => { @@ -481,6 +562,7 @@ describe('Agent Service', () => { ',"startTime":5325543453454,"operatingDuration":534535435435,"cpuUsage":35,"memoryUsage":45}]' const microserviceStatus = { + 'id': 'testUuid', 'containerId': 'testContainerId', 'status': 'RUNNING', 'startTime': 5325543453454, @@ -493,10 +575,22 @@ describe('Agent Service', () => { const microserviceStatusArray = [microserviceStatus] + const expectedMicroserviceUpdate = { + containerId: microserviceStatus.containerId, + status: microserviceStatus.status, + startTime: microserviceStatus.startTime, + operatingDuration: microserviceStatus.operatingDuration, + cpuUsage: microserviceStatus.cpuUsage, + memoryUsage: microserviceStatus.memoryUsage, + percentage: microserviceStatus.percentage, + errorMessage: microserviceStatus.errorMessage, + } + const fogStatus = { daemonStatus: 'RUNNING', daemonOperatingDuration: 25, daemonLastStart: 15325235253, + warningMessage: '', memoryUsage: 15, diskUsage: 16, cpuUsage: 17, @@ -506,26 +600,28 @@ describe('Agent Service', () => { systemAvailableDisk: 1, systemAvailableMemory: 1, systemTotalCpu: 1.1, - securityStatus: 'OK', - securityViolationInfo: '', repositoryCount: 5, - repositoryStatus: 'testStatus', + repositoryStatus: '[]', systemTime: 15325235253, lastStatusTime: 15325235253, ipAddress: 'testIpAddress', ipAddressExternal: 'testIpAddressExternal', - processedMessages: 155, - microserviceMessageCounts: 'testMessageCounts', - messageSpeed: 255, + microserviceMessageCounts: '[]', + availableRuntimes: ['edgelet'], + runtimeAgentPhase: 'Running', + controlPlaneQuiesced: false, lastCommandTime: 15325235253, - tunnelStatus: 'testTunnelStatus', + tunnelStatus: '{}', version: '1.0.0', isReadyToUpgrade: false, isReadyToRollback: false, + activeVolumeMounts: [], + volumeMountLastUpdate: 15325235253, + gpsStatus: 'OK', microserviceStatus: microservicesStatus, } - const agentStatus = { + const expectedFogUpdate = { daemonStatus: 'RUNNING', daemonOperatingDuration: 25, daemonLastStart: 15325235253, @@ -539,21 +635,22 @@ describe('Agent Service', () => { systemAvailableMemory: 1, systemTotalCpu: 1.1, securityStatus: 'OK', - securityViolationInfo: '', + securityViolationInfo: 'No violation', repositoryCount: 5, - repositoryStatus: 'testStatus', + repositoryStatus: '[]', systemTime: 15325235253, lastStatusTime: 15325235253, ipAddress: 'testIpAddress', ipAddressExternal: 'testIpAddressExternal', - processedMessages: 155, - microserviceMessageCounts: 'testMessageCounts', - messageSpeed: 255, + availableRuntimes: '["edgelet"]', lastCommandTime: 15325235253, - tunnelStatus: 'testTunnelStatus', + tunnelStatus: '{}', version: '1.0.0', isReadyToUpgrade: false, isReadyToRollback: false, + activeVolumeMounts: [], + volumeMountLastUpdate: 15325235253, + gpsStatus: 'OK', } const transaction = {} @@ -574,8 +671,8 @@ describe('Agent Service', () => { def('subject', () => $subject.updateAgentStatus(fogStatus, $fog, transaction)) def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse', () => agentStatus) def('deleteUndefinedFieldsResponse2', () => microserviceStatus) + def('findOneResponse', () => Promise.resolve({ warningMessage: '' })) def('updateResponse', () => Promise.resolve()) def('jsonParseResponse', () => microserviceStatusArray) def('updateMicroserviceStatusesResponse', () => Promise.resolve()) @@ -584,9 +681,8 @@ describe('Agent Service', () => { beforeEach(() => { $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields') - .onFirstCall().returns($deleteUndefinedFieldsResponse) - .onSecondCall().returns($deleteUndefinedFieldsResponse2) + $sandbox.spy(AppHelper, 'deleteUndefinedFields') + $sandbox.stub(ioFogManager, 'findOne').returns($findOneResponse) $sandbox.stub(ioFogManager, 'update').returns($updateResponse) $sandbox.stub(JSON, 'parse').returns($jsonParseResponse) $sandbox.stub(MicroserviceStatusManager, 'update').returns($updateMicroserviceStatusesResponse) @@ -608,15 +704,17 @@ describe('Agent Service', () => { }) context('when Validator#validate() succeeds', () => { - it('calls AppHelper.deleteUndefinedFields with correct args', async () => { + it('stringifies availableRuntimes for fog persistence', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(microserviceStatus) + expect(ioFogManager.update).to.have.been.calledWith({ + uuid: $uuid, + }, sinon.match.has('availableRuntimes', '["edgelet"]'), transaction) }) context('when AppHelper#deleteUndefinedFields fails', () => { const error = 'Error!' - def('$deleteUndefinedFieldsResponse', () => error) + def('deleteUndefinedFieldsResponse2', () => error) it(`fails with "${error}"`, () => { return expect($subject).to.be.rejectedWith = (error) @@ -628,7 +726,7 @@ describe('Agent Service', () => { await $subject expect(ioFogManager.update).to.have.been.calledWith({ uuid: $uuid, - }, agentStatus, transaction) + }, expectedFogUpdate, transaction) }) context('when ioFogManager#update fails', () => { @@ -660,7 +758,7 @@ describe('Agent Service', () => { context('when JSON#parse succeeds', () => { it('calls AppHelper.deleteUndefinedFields with correct args', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(microserviceStatus) + expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(expectedMicroserviceUpdate) }) context('when AppHelper#deleteUndefinedFields fails', () => { @@ -678,7 +776,7 @@ describe('Agent Service', () => { await $subject expect(MicroserviceStatusManager.update).to.have.been.calledWith({ microserviceUuid: microserviceStatus.id, - }, microserviceStatus, transaction) + }, expectedMicroserviceUpdate, transaction) assert.equal(microserviceStatus.percentage, 50.5) assert.equal(microserviceStatus.errorMessage, '') }) @@ -726,6 +824,7 @@ describe('Agent Service', () => { ',"startTime":5325543453454,"operatingDuration":534535435435,"cpuUsage":35,"memoryUsage":45}]' const microserviceStatus = { + 'id': 'testUuid', 'containerId': 'testContainerId', 'status': 'EXITING', 'startTime': 5325543453454, @@ -738,10 +837,22 @@ describe('Agent Service', () => { const microserviceStatusArray = [microserviceStatus] + const expectedMicroserviceUpdate = { + containerId: microserviceStatus.containerId, + status: microserviceStatus.status, + startTime: microserviceStatus.startTime, + operatingDuration: microserviceStatus.operatingDuration, + cpuUsage: microserviceStatus.cpuUsage, + memoryUsage: microserviceStatus.memoryUsage, + percentage: microserviceStatus.percentage, + errorMessage: microserviceStatus.errorMessage, + } + const fogStatus = { daemonStatus: 'RUNNING', daemonOperatingDuration: 25, daemonLastStart: 15325235253, + warningMessage: '', memoryUsage: 15, diskUsage: 16, cpuUsage: 17, @@ -751,26 +862,28 @@ describe('Agent Service', () => { systemAvailableDisk: 1, systemAvailableMemory: 1, systemTotalCpu: 1.1, - securityStatus: 'OK', - securityViolationInfo: '', repositoryCount: 5, - repositoryStatus: 'testStatus', + repositoryStatus: '[]', systemTime: 15325235253, lastStatusTime: 15325235253, ipAddress: 'testIpAddress', ipAddressExternal: 'testIpAddressExternal', - processedMessages: 155, - microserviceMessageCounts: 'testMessageCounts', - messageSpeed: 255, + microserviceMessageCounts: '[]', + availableRuntimes: ['edgelet'], + runtimeAgentPhase: 'Running', + controlPlaneQuiesced: false, lastCommandTime: 15325235253, - tunnelStatus: 'testTunnelStatus', + tunnelStatus: '{}', version: '1.0.0', isReadyToUpgrade: false, isReadyToRollback: false, + activeVolumeMounts: [], + volumeMountLastUpdate: 15325235253, + gpsStatus: 'OK', microserviceStatus: microservicesStatus, } - const agentStatus = { + const expectedFogUpdate = { daemonStatus: 'RUNNING', daemonOperatingDuration: 25, daemonLastStart: 15325235253, @@ -784,21 +897,22 @@ describe('Agent Service', () => { systemAvailableMemory: 1, systemTotalCpu: 1.1, securityStatus: 'OK', - securityViolationInfo: '', + securityViolationInfo: 'No violation', repositoryCount: 5, - repositoryStatus: 'testStatus', + repositoryStatus: '[]', systemTime: 15325235253, lastStatusTime: 15325235253, ipAddress: 'testIpAddress', ipAddressExternal: 'testIpAddressExternal', - processedMessages: 155, - microserviceMessageCounts: 'testMessageCounts', - messageSpeed: 255, + availableRuntimes: '["edgelet"]', lastCommandTime: 15325235253, - tunnelStatus: 'testTunnelStatus', + tunnelStatus: '{}', version: '1.0.0', isReadyToUpgrade: false, isReadyToRollback: false, + activeVolumeMounts: [], + volumeMountLastUpdate: 15325235253, + gpsStatus: 'OK', } def('microserviceResponse', () => ({ iofogUuid: $uuid, @@ -818,8 +932,8 @@ describe('Agent Service', () => { def('subject', () => $subject.updateAgentStatus(fogStatus, $fog, transaction)) def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse', () => agentStatus) def('deleteUndefinedFieldsResponse2', () => microserviceStatus) + def('findOneResponse', () => Promise.resolve({ warningMessage: '' })) def('updateResponse', () => Promise.resolve()) def('jsonParseResponse', () => microserviceStatusArray) def('updateMicroserviceStatusesResponse', () => Promise.resolve()) @@ -827,15 +941,13 @@ describe('Agent Service', () => { def('findMicroservice', () => Promise.resolve($microserviceResponse)) beforeEach(() => { $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields') - .onFirstCall().returns($deleteUndefinedFieldsResponse) - .onSecondCall().returns($deleteUndefinedFieldsResponse2) + $sandbox.spy(AppHelper, 'deleteUndefinedFields') + $sandbox.stub(ioFogManager, 'findOne').returns($findOneResponse) $sandbox.stub(ioFogManager, 'update').returns($updateResponse) $sandbox.stub(JSON, 'parse').returns($jsonParseResponse) $sandbox.stub(MicroserviceStatusManager, 'update').returns($updateMicroserviceStatusesResponse) $sandbox.stub(MicroserviceService, 'deleteNotRunningMicroservices').returns($deleteNotRunningResponse) $sandbox.stub(MicroserviceManager, 'findOne').returns($findMicroservice) - }) it('calls Validator#validate() with correct args', async () => { @@ -852,15 +964,17 @@ describe('Agent Service', () => { }) context('when Validator#validate() succeeds', () => { - it('calls AppHelper.deleteUndefinedFields with correct args', async () => { + it('persists Edgelet availableRuntimes on fog update', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(agentStatus) + expect(ioFogManager.update).to.have.been.calledWith({ + uuid: $uuid, + }, expectedFogUpdate, transaction) }) context('when AppHelper#deleteUndefinedFields fails', () => { const error = 'Error!' - def('$deleteUndefinedFieldsResponse', () => error) + def('deleteUndefinedFieldsResponse2', () => error) it(`fails with "${error}"`, () => { return expect($subject).to.be.rejectedWith = (error) @@ -872,7 +986,7 @@ describe('Agent Service', () => { await $subject expect(ioFogManager.update).to.have.been.calledWith({ uuid: $uuid, - }, agentStatus, transaction) + }, expectedFogUpdate, transaction) }) context('when ioFogManager#update fails', () => { @@ -904,7 +1018,7 @@ describe('Agent Service', () => { context('when JSON#parse succeeds', () => { it('calls AppHelper.deleteUndefinedFields with correct args', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(microserviceStatus) + expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(expectedMicroserviceUpdate) }) context('when AppHelper#deleteUndefinedFields fails', () => { @@ -922,7 +1036,7 @@ describe('Agent Service', () => { await $subject expect(MicroserviceStatusManager.update).to.have.been.calledWith({ microserviceUuid: microserviceStatus.id, - }, microserviceStatus, transaction) + }, expectedMicroserviceUpdate, transaction) assert.equal(microserviceStatus.percentage, 50.5) assert.equal(microserviceStatus.errorMessage, 'Error mounting volume') }) @@ -977,6 +1091,7 @@ describe('Agent Service', () => { const microserviceWithValidImage = { uuid: 'testMicroserviceUuid', + applicationId: 1, imageId: '', config: '{}', rebuild: false, @@ -989,7 +1104,7 @@ describe('Agent Service', () => { deleteWithCleanup: false, catalogItem: { images: [{ - fogTypeId: 1, + archId: 1, containerImage: 'testContainerImage', }, ], @@ -1030,7 +1145,7 @@ describe('Agent Service', () => { deleteWithCleanup: false, catalogItem: { images: [{ - fogTypeId: 3, + archId: 3, containerImage: 'testContainerImage', }, ], @@ -1071,11 +1186,11 @@ describe('Agent Service', () => { } def('uuid', () => 'testUuid') - def('fogTypeId', () => 1) + def('archId', () => 1) def('fog', () => ({ uuid: $uuid, - fogTypeId: $fogTypeId, + archId: $archId, })) def('token', () => 'testToken') @@ -1088,6 +1203,9 @@ describe('Agent Service', () => { beforeEach(() => { $sandbox.stub(MicroserviceManager, 'findAllActiveApplicationMicroservices').returns($findAllMicroservicesResponse) $sandbox.stub(MicroserviceManager, 'update').returns($updateResponse) + $sandbox.stub(ApplicationManager, 'findOne').returns(Promise.resolve({ name: 'testApp' })) + $sandbox.stub(MicroserviceService, 'isMicroserviceRouter').returns(Promise.resolve(false)) + $sandbox.stub(MicroserviceService, 'isMicroserviceNats').returns(Promise.resolve(false)) }) it('calls MicroserviceManager#findAllActiveApplicationMicroservices() with correct args', async () => { @@ -1125,8 +1243,16 @@ describe('Agent Service', () => { }) context('when MicroserviceManager#update succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.deep.equal(microserviceResponse) + it(`succeeds`, async () => { + const result = await $subject + expect(result.microservices).to.have.length(1) + const msvc = result.microservices[0] + expect(msvc.uuid).to.equal(microserviceResponse.microservices[0].uuid) + expect(msvc.imageId).to.equal(microserviceResponse.microservices[0].imageId) + expect(msvc.application).to.equal('testApp') + expect(msvc.registryId).to.equal(microserviceResponse.microservices[0].registryId) + expect(msvc.cmd).to.deep.equal(microserviceResponse.microservices[0].cmd) + expect(msvc.extraHosts).to.deep.equal(microserviceResponse.microservices[0].extraHosts) }) }) }) @@ -1220,17 +1346,7 @@ describe('Agent Service', () => { it('calls RegistryManager#findAll() with correct args', async () => { await $subject - expect(RegistryManager.findAll).to.have.been.calledWith({ - [Op.or]: - [ - { - userId: $userId, - }, - { - isPublic: true, - }, - ], - }, transaction) + expect(RegistryManager.findAll).to.have.been.calledWith({}, transaction) }) context('when RegistryManager#findAll() fails', () => { diff --git a/test/src/services/controller-service.test.js b/test/src/services/controller-service.test.js index dff7a4ab..a0256045 100644 --- a/test/src/services/controller-service.test.js +++ b/test/src/services/controller-service.test.js @@ -13,11 +13,11 @@ describe('Controller Service', () => { const isCLI = false - describe('.getFogTypes()', () => { + describe('.getArchitectures()', () => { const transaction = {} const error = 'Error!' - def('subject', () => $subject.getFogTypes(isCLI, transaction)) + def('subject', () => $subject.getArchitectures(isCLI, transaction)) def('findResponse', () => Promise.resolve([{ id: 15, name: 'testName', @@ -44,42 +44,11 @@ describe('Controller Service', () => { context('when architectureManager#findAll() succeeds', () => { it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('fogTypes') + return expect($subject).to.eventually.have.property('architectures') }) }) }) - describe('.emailActivation()', () => { - const error = 'Error!' - - def('subject', () => $subject.emailActivation(isCLI)) - def('getResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(Config, 'get').returns($getResponse) - }) - - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:ActivationEnabled') - }) - - context('when Config#get() fails', () => { - def('getResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Config#get() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('isEmailActivationEnabled') - }) - }) - }) - - /* describe('.statusController()', () => { const error = 'Error!' diff --git a/test/src/services/iofog-service.test.js b/test/src/services/iofog-service.test.js index 348051b9..c09d5263 100644 --- a/test/src/services/iofog-service.test.js +++ b/test/src/services/iofog-service.test.js @@ -49,7 +49,7 @@ describe('ioFog Service', () => { latitude: 45, longitude: 46, description: 'testDescription', - dockerUrl: 'testDockerUrl', + containerEngineUrl: 'testContainerEngineUrl', diskLimit: 15, diskDirectory: 'testDirectory', memoryLimit: 55, @@ -63,8 +63,8 @@ describe('ioFog Service', () => { bluetoothEnabled: true, watchdogEnabled: false, abstractedHardwareEnabled: true, - fogType: 1, - dockerPruningFrequency: 10, + archId: 1, + pruningFrequency: 10, availableDiskThreshold: 20, logLevel: 'INFO', isSystem: false, @@ -80,7 +80,7 @@ describe('ioFog Service', () => { longitude: fogData.longitude, gpsMode: fogData.latitude || fogData.longitude ? 'manual' : undefined, description: fogData.description, - dockerUrl: fogData.dockerUrl, + dockerUrl: fogData.containerEngineUrl, diskLimit: fogData.diskLimit, diskDirectory: fogData.diskDirectory, memoryLimit: fogData.memoryLimit, @@ -94,10 +94,9 @@ describe('ioFog Service', () => { bluetoothEnabled: fogData.bluetoothEnabled, watchdogEnabled: fogData.watchdogEnabled, abstractedHardwareEnabled: fogData.abstractedHardwareEnabled, - fogTypeId: fogData.fogType, + archId: fogData.archId, isSystem: fogData.isSystem, - userId: user.id, - dockerPruningFrequency: 10, + dockerPruningFrequency: fogData.pruningFrequency, availableDiskThreshold: 20, logLevel: 'INFO', routerId: null, @@ -118,7 +117,7 @@ describe('ioFog Service', () => { iofogUuid: createFogData.uuid, rootHostAccess: true, logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId: oldFog ? oldFog.userId : user.id, + userId: oldFog ? oldFog.userId : undefined, configLastUpdated: date, } @@ -134,7 +133,7 @@ describe('ioFog Service', () => { iofogUuid: createFogData.uuid, rootHostAccess: true, logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId: oldFog ? oldFog.userId : user.id, + userId: oldFog ? oldFog.userId : undefined, configLastUpdated: date, } @@ -157,9 +156,9 @@ describe('ioFog Service', () => { id: 1 } - def('subject', () => $subject.createFogEndPoint(fogData, user, isCLI, transaction)) + def('subject', () => $subject.createFogEndPoint(fogData, isCLI, transaction)) def('validatorResponse', () => Promise.resolve(true)) - def('generateRandomStringResponse', () => uuid) + def('generateUuidResponse', () => uuid) def('generateRandomStringResponse2', () => uuid2) def('generateRandomStringResponse3', () => uuid3) def('deleteUndefinedFieldsResponse', () => createFogData) @@ -179,10 +178,10 @@ describe('ioFog Service', () => { beforeEach(() => { $sandbox.stub(Validator, 'validate').returns($validatorResponse) + $sandbox.stub(AppHelper, 'generateUUID').returns($generateUuidResponse) $sandbox.stub(AppHelper, 'generateRandomString') - .onFirstCall().returns($generateRandomStringResponse) - .onSecondCall().returns($generateRandomStringResponse2) - .onThirdCall().returns($generateRandomStringResponse3) + .onFirstCall().returns($generateRandomStringResponse2) + .onSecondCall().returns($generateRandomStringResponse3) $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) $sandbox.stub(ioFogManager, 'create').returns($createIoFogResponse) $sandbox.stub(ChangeTrackingService, 'create').returns($createChangeTrackingResponse) @@ -219,20 +218,20 @@ describe('ioFog Service', () => { }) context('when Validator#validate() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { + it('calls AppHelper#generateUUID()', async () => { await $subject - expect(AppHelper.generateRandomString).to.have.been.calledWith(32) + expect(AppHelper.generateUUID).to.have.been.called }) - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse', () => error) + context('when AppHelper#generateUUID() fails', () => { + def('generateUuidResponse', () => { throw error }) it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('uuid') + return expect($subject).to.be.rejectedWith(error) }) }) - context('when AppHelper#generateRandomString() succeeds', () => { + context('when AppHelper#generateUUID() succeeds', () => { it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { await $subject @@ -440,7 +439,7 @@ describe('ioFog Service', () => { context('when routerMode is edge or interior', () => { it('expects router to be created', async () => { await $subject - return expect(RouterService.createRouterForFog).to.have.been.calledWith({...fogData, routerMode: 'edge'}, response.uuid, user.id, []) + return expect(RouterService.createRouterForFog).to.have.been.calledWith({...fogData, routerMode: 'edge'}, response.uuid, []) }) }) }) @@ -469,7 +468,7 @@ describe('ioFog Service', () => { latitude: 45, longitude: 46, description: 'testDescription', - dockerUrl: 'testDockerUrl', + containerEngineUrl: 'testContainerEngineUrl', diskLimit: 15, diskDirectory: 'testDirectory', memoryLimit: 55, @@ -483,8 +482,8 @@ describe('ioFog Service', () => { bluetoothEnabled: true, watchdogEnabled: false, abstractedHardwareEnabled: true, - fogType: 1, - dockerPruningFrequency: 90, + archId: 1, + pruningFrequency: 90, availableDiskThreshold: 10, logLevel: 'INFO', isSystem: true, @@ -499,7 +498,7 @@ describe('ioFog Service', () => { latitude: 45, longitude: 46, description: 'testDescription', - dockerUrl: 'testDockerUrl', + containerEngineUrl: 'testContainerEngineUrl', diskLimit: 15, diskDirectory: 'testDirectory', memoryLimit: 55, @@ -513,8 +512,8 @@ describe('ioFog Service', () => { bluetoothEnabled: false, watchdogEnabled: false, abstractedHardwareEnabled: false, - fogType: 1, - dockerPruningFrequency: 90, + archId: 1, + pruningFrequency: 90, availableDiskThreshold: 10, logLevel: 'INFO', isSystem: false, @@ -532,7 +531,7 @@ describe('ioFog Service', () => { longitude: fogData.longitude, gpsMode: fogData.latitude || fogData.longitude ? 'manual' : undefined, description: fogData.description, - dockerUrl: fogData.dockerUrl, + dockerUrl: fogData.containerEngineUrl, diskLimit: fogData.diskLimit, diskDirectory: fogData.diskDirectory, memoryLimit: fogData.memoryLimit, @@ -546,8 +545,8 @@ describe('ioFog Service', () => { bluetoothEnabled: fogData.bluetoothEnabled, watchdogEnabled: fogData.watchdogEnabled, abstractedHardwareEnabled: fogData.abstractedHardwareEnabled, - fogTypeId: fogData.fogType, - dockerPruningFrequency: 90, + archId: fogData.archId, + dockerPruningFrequency: fogData.pruningFrequency, availableDiskThreshold: 10, logLevel: 'INFO', isSystem: fogData.isSystem, @@ -566,7 +565,7 @@ describe('ioFog Service', () => { iofogUuid: fogData.uuid, rootHostAccess: true, logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId: oldFog ? oldFog.userId : user.id, + userId: oldFog ? oldFog.userId : undefined, configLastUpdated: date, } @@ -582,7 +581,7 @@ describe('ioFog Service', () => { iofogUuid: fogData.uuid, rootHostAccess: true, logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId: oldFog ? oldFog.userId : user.id, + userId: oldFog ? oldFog.userId : undefined, configLastUpdated: date, } @@ -604,7 +603,7 @@ describe('ioFog Service', () => { id: 42 } - def('subject', () => $subject.updateFogEndPoint(fogData, user, isCLI, transaction)) + def('subject', () => $subject.updateFogEndPoint(fogData, isCLI, transaction)) def('validatorResponse', () => Promise.resolve(true)) def('deleteUndefinedFieldsResponse', () => ({...updateFogData})) def('findIoFogResponse', () => Promise.resolve({...oldFog, getRouter: () => Promise.resolve(router)})) @@ -633,10 +632,10 @@ describe('ioFog Service', () => { $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) $sandbox.stub(ioFogManager, 'findOne') .withArgs({ uuid: uuid }).returns($findIoFogResponse) - .withArgs({ name: 'new-name', uuid: { [Op.not]: 'testUuid' }, userId: user.id }).returns(Promise.resolve()) + .withArgs({ name: 'new-name', uuid: { [Op.not]: 'testUuid' } }).returns(Promise.resolve()) $sandbox.stub(ioFogManager, 'findOneWithTags') .withArgs({ uuid: uuid }).returns($findIoFogResponse) - .withArgs({ name: 'new-name', uuid: { [Op.not]: 'testUuid' }, userId: user.id }).returns(Promise.resolve()) + .withArgs({ name: 'new-name', uuid: { [Op.not]: 'testUuid' } }).returns(Promise.resolve()) $sandbox.stub(ioFogManager, 'update').returns($updateIoFogResponse) $sandbox.stub(ChangeTrackingService, 'update') .onFirstCall().returns($updateChangeTrackingResponse) @@ -711,7 +710,7 @@ describe('ioFog Service', () => { await $subject expect(ioFogManager.update).to.have.been.calledWith(queryFogData, - {...updateFogData, userId: 0, routerId: router.id}) + {...updateFogData, routerId: router.id}) }) context('when ioFogManager#update() fails', () => { @@ -953,7 +952,7 @@ describe('ioFog Service', () => { }) it('should set the network router', async () => { await $subject - return expect(ioFogManager.update).to.have.been.calledWith(queryFogData, {...updateFogData, userId: 0, routerId: networkRouter.id}) + return expect(ioFogManager.update).to.have.been.calledWith(queryFogData, {...updateFogData, routerId: networkRouter.id}) }) }) }) @@ -973,7 +972,7 @@ describe('ioFog Service', () => { def('findIoFogResponse', () => Promise.resolve({...oldFog, getRouter: () => Promise.resolve(null)})) it('Should create a router', async () => { await $subject - return expect(RouterService.createRouterForFog).to.have.been.calledWith(fogData, oldFog.uuid, user.id, []) + return expect(RouterService.createRouterForFog).to.have.been.calledWith(fogData, oldFog.uuid, []) }) }) @@ -1011,7 +1010,7 @@ describe('ioFog Service', () => { latitude: 45, longitude: 46, description: 'testDescription', - dockerUrl: 'testDockerUrl', + containerEngineUrl: 'testContainerEngineUrl', diskLimit: 15, diskDirectory: 'testDirectory', daemonStatus: 'RUNNING', @@ -1028,7 +1027,7 @@ describe('ioFog Service', () => { bluetoothEnabled: false, watchdogEnabled: false, abstractedHardwareEnabled: false, - fogType: 1, + archId: 1, userId: user.id } @@ -1050,7 +1049,7 @@ describe('ioFog Service', () => { const queryFogData = { uuid: fogData.uuid } - def('subject', () => $subject.deleteFogEndPoint(fogData, user, isCLI, transaction)) + def('subject', () => $subject.deleteFogEndPoint(fogData, isCLI, transaction)) def('validatorResponse', () => Promise.resolve(true)) def('findIoFogResponse', () => Promise.resolve(fog)) def('updateChangeTrackingResponse', () => Promise.resolve()) @@ -1203,7 +1202,7 @@ describe('ioFog Service', () => { latitude: 45, longitude: 46, description: 'testDescription', - dockerUrl: 'testDockerUrl', + containerEngineUrl: 'testContainerEngineUrl', diskLimit: 15, diskDirectory: 'testDirectory', daemonStatus: 'RUNNING', @@ -1220,7 +1219,7 @@ describe('ioFog Service', () => { bluetoothEnabled: false, watchdogEnabled: false, abstractedHardwareEnabled: false, - fogType: 1, + archId: 1, userId: user.id, routerMode: 'none', edgeResources: [], @@ -1235,7 +1234,7 @@ describe('ioFog Service', () => { isDefault: true } - def('subject', () => $subject.getFog(fogData, user, isCLI, transaction)) + def('subject', () => $subject.getFog(fogData, isCLI, transaction)) def('validatorResponse', () => Promise.resolve(true)) def('findIoFogResponse', () => Promise.resolve({...fog, getRouter: () => Promise.resolve(null), toJSON: () => fog, getEdgeResources: () => Promise.resolve([])})) def('findOneRouterResponse', () => Promise.resolve(null)) @@ -1345,7 +1344,7 @@ describe('ioFog Service', () => { latitude: 45, longitude: 46, description: 'testDescription', - dockerUrl: 'testDockerUrl', + containerEngineUrl: 'testContainerEngineUrl', diskLimit: 15, diskDirectory: 'testDirectory', daemonStatus: 'RUNNING', @@ -1362,18 +1361,18 @@ describe('ioFog Service', () => { bluetoothEnabled: false, watchdogEnabled: false, abstractedHardwareEnabled: false, - fogType: 1, + archId: 1, } const isSystem = false const fogs = [fog] - const queryFogData = isSystem ? { isSystem } : (isCLI ? {} : { userId: user.id, isSystem: false }) + const queryFogData = {} const filters = [] - def('subject', () => $subject.getFogListEndPoint(filters, user, isCLI, isSystem, transaction)) + def('subject', () => $subject.getFogListEndPoint(filters, isCLI, transaction)) def('validatorResponse', () => Promise.resolve(true)) def('findAllIoFogResponse', () => Promise.resolve(fogs.map(f => ({...f, getRouter: () => Promise.resolve(null), getEdgeResources: () => Promise.resolve([]), toJSON: () => f})))) def('findOneRouterResponse', () => Promise.resolve(null)) @@ -1448,9 +1447,9 @@ describe('ioFog Service', () => { expirationTime: expirationTime, } - def('subject', () => $subject.generateProvisioningKeyEndPoint(fogData, user, isCLI, transaction)) + def('subject', () => $subject.generateProvisioningKeyEndPoint(fogData, isCLI, transaction)) def('validatorResponse', () => Promise.resolve(true)) - def('generateRandomStringResponse', () => provisionKey) + def('generateUuidResponse', () => provisionKey) def('findIoFogResponse', () => Promise.resolve({ uuid: fogData.uuid, userId: user.id })) def('updateOrCreateProvisionKeyResponse', () => Promise.resolve(newProvision)) @@ -1458,7 +1457,7 @@ describe('ioFog Service', () => { beforeEach(() => { $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'generateRandomString').returns($generateRandomStringResponse) + $sandbox.stub(AppHelper, 'generateUUID').returns($generateUuidResponse) $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) $sandbox.stub(ioFogProvisionKeyManager, 'updateOrCreate').returns($updateOrCreateProvisionKeyResponse) @@ -1480,14 +1479,14 @@ describe('ioFog Service', () => { }) context('when Validator#validate() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { + it('calls AppHelper#generateUUID() with correct args', async () => { await $subject - expect(AppHelper.generateRandomString).to.have.been.calledWith(8) + expect(AppHelper.generateUUID).to.have.been.called }) - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse', () => error) + context('when AppHelper#generateUUID() fails', () => { + def('generateUuidResponse', () => { throw error }) it(`fails with ${error}`, () => { return expect($subject).to.eventually.have.property('key') @@ -1577,10 +1576,10 @@ describe('ioFog Service', () => { expirationTime: expirationTime, } - def('subject', () => $subject.setFogVersionCommandEndPoint(fogVersionData, user, isCLI, transaction)) + def('subject', () => $subject.setFogVersionCommandEndPoint(fogVersionData, isCLI, transaction)) def('validatorResponse', () => Promise.resolve(true)) def('findIoFogResponse', () => Promise.resolve(ioFog)) - def('generateRandomStringResponse', () => provisionKey) + def('generateUuidResponse', () => provisionKey) def('updateOrCreateProvisionKeyResponse', () => Promise.resolve(newProvision)) def('findIoFogVersionCommandResponse', () => Promise.resolve()) def('updateChangeTrackingResponse', () => Promise.resolve()) @@ -1591,7 +1590,7 @@ describe('ioFog Service', () => { $sandbox.stub(Validator, 'validate').returns($validatorResponse) $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - $sandbox.stub(AppHelper, 'generateRandomString').returns($generateRandomStringResponse) + $sandbox.stub(AppHelper, 'generateUUID').returns($generateUuidResponse) $sandbox.stub(ioFogProvisionKeyManager, 'updateOrCreate').returns($updateOrCreateProvisionKeyResponse) $sandbox.stub(ioFogVersionCommandManager, 'updateOrCreate').returns($findIoFogVersionCommandResponse) $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) @@ -1750,7 +1749,7 @@ describe('ioFog Service', () => { const queryFogData = { uuid: fogData.uuid } - def('subject', () => $subject.setFogRebootCommandEndPoint(fogData, user, isCLI, transaction)) + def('subject', () => $subject.setFogRebootCommandEndPoint(fogData, isCLI, transaction)) def('validatorResponse', () => Promise.resolve(true)) def('findIoFogResponse', () => Promise.resolve({ uuid: fogData.uuid, userId: user.id })) def('updateChangeTrackingResponse', () => Promise.resolve()) @@ -1831,7 +1830,7 @@ describe('ioFog Service', () => { uuid: uuid, } - def('subject', () => $subject.getHalHardwareInfoEndPoint(uuidObj, user, isCLI, transaction)) + def('subject', () => $subject.getHalHardwareInfoEndPoint(uuidObj, isCLI, transaction)) def('validatorResponse', () => Promise.resolve(true)) def('findIoFogResponse', () => Promise.resolve({ userId: user.id, uuid: uuidObj.uuid })) def('findHalHardwareResponse', () => Promise.resolve()) @@ -1915,7 +1914,7 @@ describe('ioFog Service', () => { uuid: uuid, } - def('subject', () => $subject.getHalUsbInfoEndPoint(uuidObj, user, isCLI, transaction)) + def('subject', () => $subject.getHalUsbInfoEndPoint(uuidObj, isCLI, transaction)) def('validatorResponse', () => Promise.resolve(true)) def('findIoFogResponse', () => Promise.resolve({ userId: user.id, uuid: uuidObj.uuid })) def('findHalUsbResponse', () => Promise.resolve()) From 2584ef32ec87a326453f5aca98fba32618566e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 04:17:29 +0300 Subject: [PATCH 12/75] Remove legacy /api/v3/flow route. --- src/routes/flow.js | 176 --------------------------------------------- 1 file changed, 176 deletions(-) delete mode 100644 src/routes/flow.js diff --git a/src/routes/flow.js b/src/routes/flow.js deleted file mode 100644 index 3de28156..00000000 --- a/src/routes/flow.js +++ /dev/null @@ -1,176 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ -const constants = require('../helpers/constants') -const FlowController = require('../controllers/application-controller') -const ResponseDecorator = require('../decorators/response-decorator') -const Errors = require('../helpers/errors') -const logger = require('../logger') -const rbacMiddleware = require('../lib/rbac/middleware') - -module.exports = [ - { - method: 'get', - path: '/api/v3/flow', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for both SRE and Developer roles - await rbacMiddleware.protect()(req, res, async () => { - const getFlowsByUserEndPoint = ResponseDecorator.handleErrors(FlowController.getApplicationsByUserEndPoint, successCode, errorCodes) - const responseObject = await getFlowsByUserEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send({ flows: responseObject.body.applications }) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'post', - path: '/api/v3/flow', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_CREATED - const errorCodes = [ - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - }, - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for both SRE and Developer roles - await rbacMiddleware.protect()(req, res, async () => { - const createFlowEndPoint = ResponseDecorator.handleErrors(FlowController.createApplicationEndPoint, successCode, errorCodes) - const responseObject = await createFlowEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'get', - path: '/api/v3/flow/:id', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for both SRE and Developer roles - await rbacMiddleware.protect()(req, res, async () => { - const getFlowEndPoint = ResponseDecorator.handleErrors(FlowController.getApplicationByIdEndPoint, successCode, errorCodes) - const responseObject = await getFlowEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'patch', - path: '/api/v3/flow/:id', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_NO_CONTENT - const errorCodes = [ - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - }, - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for both SRE and Developer roles - await rbacMiddleware.protect()(req, res, async () => { - const updateFlowEndPoint = ResponseDecorator.handleErrors(FlowController.patchApplicationByIdEndPoint, successCode, errorCodes) - const responseObject = await updateFlowEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'delete', - path: '/api/v3/flow/:id', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_NO_CONTENT - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for both SRE and Developer roles - await rbacMiddleware.protect()(req, res, async () => { - const deleteFlowEndPoint = ResponseDecorator.handleErrors(FlowController.deleteApplicationByIdEndPoint, successCode, errorCodes) - const responseObject = await deleteFlowEndPoint(req) - const user = req.kauth.grant.access_token.content.preferred_username - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - } -] From cf8458396c1c39af28a97dbd3a150c76bb708354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 04:17:35 +0300 Subject: [PATCH 13/75] Remove edge resource HTTP API and fog integration. Drop edge resource routes, service stack, template filter, capabilities probe, and edgeResources from fog extra info. --- src/controllers/edge-resource-controller.js | 73 ----- src/data/models/fog.js | 1 - src/helpers/template-helper.js | 24 +- src/routes/capabilities.js | 12 - src/routes/edgeResource.js | 275 ----------------- src/schemas/edgeResource.js | 97 ------ src/services/edge-resource-service.js | 322 -------------------- src/services/iofog-service.js | 18 +- 8 files changed, 3 insertions(+), 819 deletions(-) delete mode 100644 src/controllers/edge-resource-controller.js delete mode 100644 src/routes/edgeResource.js delete mode 100644 src/schemas/edgeResource.js delete mode 100644 src/services/edge-resource-service.js diff --git a/src/controllers/edge-resource-controller.js b/src/controllers/edge-resource-controller.js deleted file mode 100644 index 68ffb560..00000000 --- a/src/controllers/edge-resource-controller.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const EdgeResourceService = require('../services/edge-resource-service') - -const createEdgeResourceEndpoint = async function (req) { - const edgeResourceData = req.body - return EdgeResourceService.createEdgeResource(edgeResourceData) -} - -const updateEdgeResourceEndpoint = async function (req) { - const edgeResourceData = req.body - const { version, name } = req.params - return EdgeResourceService.updateEdgeResourceEndpoint(edgeResourceData, { name, version }) -} - -const listEdgeResourcesEndpoint = async function () { - return { edgeResources: await EdgeResourceService.listEdgeResources() } -} - -const getEdgeResourceEndpoint = async function (req) { - const { version, name } = req.params - const result = await EdgeResourceService.getEdgeResource({ name, version }) - if (version) { - return result - } else { - return { edgeResources: result } - } -} - -const getEdgeResourceAllVersionsEndpoint = async function (req) { - const { name } = req.params - const result = await EdgeResourceService.getEdgeResource({ name }) - return { edgeResources: result } -} - -const deleteEdgeResourceEndpoint = async function (req) { - const { version, name } = req.params - return EdgeResourceService.deleteEdgeResource({ name, version }) -} - -const linkEdgeResourceEndpoint = async function (req) { - const { name, version } = req.params - const { uuid } = req.body - return EdgeResourceService.linkEdgeResource({ name, version }, uuid) -} - -const unlinkEdgeResourceEndpoint = async function (req) { - const { name, version } = req.params - const { uuid } = req.body - return EdgeResourceService.unlinkEdgeResource({ name, version }, uuid) -} - -module.exports = { - createEdgeResourceEndpoint: (createEdgeResourceEndpoint), - updateEdgeResourceEndpoint: (updateEdgeResourceEndpoint), - listEdgeResourcesEndpoint: (listEdgeResourcesEndpoint), - getEdgeResourceEndpoint: (getEdgeResourceEndpoint), - deleteEdgeResourceEndpoint: (deleteEdgeResourceEndpoint), - linkEdgeResourceEndpoint: (linkEdgeResourceEndpoint), - unlinkEdgeResourceEndpoint: (unlinkEdgeResourceEndpoint), - getEdgeResourceAllVersionsEndpoint: (getEdgeResourceAllVersionsEndpoint) -} diff --git a/src/data/models/fog.js b/src/data/models/fog.js index 6ce2c0e6..bc8fcd09 100644 --- a/src/data/models/fog.js +++ b/src/data/models/fog.js @@ -398,7 +398,6 @@ module.exports = (sequelize, DataTypes) => { }) Fog.belongsToMany(models.Tags, { through: 'IofogTags', as: 'tags' }) - Fog.belongsToMany(models.EdgeResource, { through: 'AgentEdgeResources', as: 'edgeResources' }) Fog.belongsToMany(models.VolumeMount, { through: 'FogVolumeMounts', as: 'volumeMounts' }) } diff --git a/src/helpers/template-helper.js b/src/helpers/template-helper.js index a75ba093..89f1e9f1 100755 --- a/src/helpers/template-helper.js +++ b/src/helpers/template-helper.js @@ -14,7 +14,6 @@ const ApplicationManager = require('../data/managers/application-manager.js') // Using manager instead of service to avoid dependency loop const FogService = require('../services/iofog-service') const MicroservicesService = require('../services/microservices-service') -const EdgeResourceService = require('../services/edge-resource-service') // ninja2 like template engine const { Liquid } = require('../lib/liquidjs/liquid.node.cjs') @@ -34,23 +33,6 @@ function findMicroserviceAgentHandler (microservice) { return result } -async function findEdgeResourcehandler (name, version) { - const key = `${name}/${version}` - // const user = this.context.environments._user - // if (!user) { - // return undefined - // } - if (this.context.environments._edgeResourcesByName && this.context.environments._edgeResourcesByName[key]) { - return this.context.environments._edgeResourcesByName[key] - } - const result = await EdgeResourceService.getEdgeResource({ name, version }) - - if (result && this.context.environments._edgeResourcesByName) { - this.context.environments._edgeResourcesByName[key] = result - } - return result -} - async function findApplicationHandler (name) { // const user = this.context.environments._user // if (!user) { @@ -117,11 +99,10 @@ function toStringParser (variable) { } } /** - * Add filter findEdgeRessource to template engine. + * Add filter findApplication to template engine. * user is in liquid context _user - * Syntaxe {{ name findEdgeRessource: version }} + * Syntaxe {{ name | findApplication }} */ -templateEngine.registerFilter('findEdgeResource', findEdgeResourcehandler) templateEngine.registerFilter('findApplication', findApplicationHandler) templateEngine.registerFilter('findAgent', findAgentHandler) templateEngine.registerFilter('findMicroserviceAgent', findMicroserviceAgentHandler) @@ -145,7 +126,6 @@ const rvaluesVarSubstition = async (subjects, templateContext) => { // Create local cache for filters if they do not exists context._agentsByName = context._agentsByName || {} - context._edgeResourcesByName = context._edgeResourcesByName || {} context._applicationsByName = context._applicationsByName || {} for (let key in subjects) { diff --git a/src/routes/capabilities.js b/src/routes/capabilities.js index 9f576ae1..847eb2cb 100644 --- a/src/routes/capabilities.js +++ b/src/routes/capabilities.js @@ -15,18 +15,6 @@ const config = require('../config') const rbacMiddleware = require('../lib/rbac/middleware') module.exports = [ - { - method: 'head', - path: '/api/v3/capabilities/edgeResources', - middleware: async (req, res) => { - logger.apiReq(req) - - // Add rbacMiddleware.protect middleware to protect the route - await rbacMiddleware.protect()(req, res, async () => { - res.sendStatus(204) - }) - } - }, { method: 'head', path: '/api/v3/capabilities/applicationTemplates', diff --git a/src/routes/edgeResource.js b/src/routes/edgeResource.js deleted file mode 100644 index 54208811..00000000 --- a/src/routes/edgeResource.js +++ /dev/null @@ -1,275 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ -const constants = require('../helpers/constants') -const EdgeResourceController = require('../controllers/edge-resource-controller') -const ResponseDecorator = require('../decorators/response-decorator') -const logger = require('../logger') -const Errors = require('../helpers/errors') -const rbacMiddleware = require('../lib/rbac/middleware') - -module.exports = [ - { - method: 'get', - path: '/api/v3/edgeResources', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const getEdgeResourcesEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.listEdgeResourcesEndpoint, successCode, errorCodes) - const responseObject = await getEdgeResourcesEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'get', - path: '/api/v3/edgeResource/:name/:version', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const getEdgeResourceEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.getEdgeResourceEndpoint, successCode, errorCodes) - const responseObject = await getEdgeResourceEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'get', - path: '/api/v3/edgeResource/:name', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const getEdgeResourceAllVersionsEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.getEdgeResourceAllVersionsEndpoint, successCode, errorCodes) - const responseObject = await getEdgeResourceAllVersionsEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'put', - path: '/api/v3/edgeResource/:name/:version', - supportSubstitution: true, - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const updateEdgeResourceEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.updateEdgeResourceEndpoint, successCode, errorCodes) - const responseObject = await updateEdgeResourceEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'delete', - path: '/api/v3/edgeResource/:name/:version', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_ACCEPTED - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const deleteEdgeResourceEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.deleteEdgeResourceEndpoint, successCode, errorCodes) - const responseObject = await deleteEdgeResourceEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'post', - path: '/api/v3/edgeResource', - supportSubstitution: true, - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const createEdgeResourceEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.createEdgeResourceEndpoint, successCode, errorCodes) - const responseObject = await createEdgeResourceEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'post', - path: '/api/v3/edgeResource/:name/:version/link', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const linkEdgeResourceEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.linkEdgeResourceEndpoint, successCode, errorCodes) - const responseObject = await linkEdgeResourceEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'delete', - path: '/api/v3/edgeResource/:name/:version/link', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const unlinkEdgeResourceEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.unlinkEdgeResourceEndpoint, successCode, errorCodes) - const responseObject = await unlinkEdgeResourceEndpoint(req) - const user = req.kauth.grant.access_token.content.preferred_username - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - } -] diff --git a/src/schemas/edgeResource.js b/src/schemas/edgeResource.js deleted file mode 100644 index 1bc88779..00000000 --- a/src/schemas/edgeResource.js +++ /dev/null @@ -1,97 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ -const { nameRegex, colorRegex, versionRegex } = require('./utils/utils') - -const edgeResourceCreate = { - 'id': '/edgeResourceCreate', - 'type': 'object', - 'allOf': [ - { '$ref': '/edgeResource' } - ], - required: ['name', 'version'], - additionalProperties: true -} - -const edgeResourceUpdate = { - 'id': '/edgeResourceUpdate', - 'type': 'object', - 'allOf': [ - { '$ref': '/edgeResource' } - ], - additionalProperties: true -} - -const edgeResource = { - 'id': '/edgeResource', - 'type': 'object', - 'properties': { - 'display': { '$ref': '/edgeResourceDisplay' }, - name: { - type: 'string', - 'minLength': 1, - 'pattern': nameRegex - }, - version: { - type: 'string', - 'minLength': 1, - 'pattern': versionRegex - }, - description: { type: 'string' }, - orchestrationTags: { type: 'array', items: { type: 'string' } }, - interfaceProtocol: { 'enum': ['http', 'https', 'ws', 'wss'] } - }, - oneOf: [ - { - properties: { - interfaceProtocol: { enum: ['http', 'https', 'ws', 'wss'] }, - interface: { - type: 'object', - properties: { - endpoints: { type: 'array', items: { '$ref': '/edgeResourceHTTPEndpoint' } } - } - } - } - } - ] -} - -const edgeResourceHTTPEndpoint = { - id: '/edgeResourceHTTPEndpoint', - type: 'object', - properties: { - name: { type: 'string', pattern: nameRegex }, - description: { type: 'string' }, - method: { enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] }, - url: { type: 'string' }, - requestType: { type: 'string' }, - responseType: { type: 'string' }, - requestPayloadExample: { type: 'string' }, - responsePayloadExample: { type: 'string' } - } -} - -const edgeResourceDisplay = { - 'id': '/edgeResourceDisplay', - 'type': 'object', - properties: { - name: { type: 'string' }, - color: { type: 'string', pattern: colorRegex }, - icon: { type: 'string' } - }, - additionalProperties: true -} - -module.exports = { - mainSchemas: [edgeResourceCreate, edgeResourceUpdate], - innerSchemas: [edgeResourceDisplay, edgeResourceHTTPEndpoint, edgeResource] -} diff --git a/src/services/edge-resource-service.js b/src/services/edge-resource-service.js deleted file mode 100644 index 10e943ca..00000000 --- a/src/services/edge-resource-service.js +++ /dev/null @@ -1,322 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const AppHelper = require('../helpers/app-helper') -const Errors = require('../helpers/errors') -const ErrorMessages = require('../helpers/error-messages') -const EdgeResourceManager = require('../data/managers/edge-resource-manager') -const TagsManager = require('../data/managers/tags-manager') -const HTTPBasedResourceInterfaceManager = require('../data/managers/http-based-resource-interface-manager') -const HTTPBasedResourceInterfaceEndpointsManager = require('../data/managers/http-based-resource-interface-endpoints-manager') -const FogManager = require('../data/managers/iofog-manager') -const TransactionDecorator = require('../decorators/transaction-decorator') -const Validator = require('../schemas') -const ChangeTrackingService = require('./change-tracking-service') - -async function listEdgeResources () { - const edgeResources = await EdgeResourceManager.findAllWithOrchestrationTags() - return edgeResources.map(buildGetObject) -} - -async function getEdgeResource ({ name, version }, transaction) { - if (version) { - const resource = await EdgeResourceManager.findOneWithOrchestrationTags({ name, version }, transaction) - if (!resource) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.NOT_FOUND_RESOURCE_NAME_VERSION, name, version)) - } - const intrface = await getInterface(resource, transaction) - // Transform Sequelize objects into plain JSON objects - const result = { ...resource.toJSON(), interface: (intrface || { toJSON: () => {} }).toJSON() } - return buildGetObject(result) - } else { - const resources = await EdgeResourceManager.findAllWithOrchestrationTags({ name }, transaction) - if (!resources.length) { - return [] - } - let result = [] - for (const resource of resources) { - const intrface = await getInterface(resource) - // Transform Sequelize objects into plain JSON objects - result.push(buildGetObject({ ...resource.toJSON(), interface: intrface.toJSON() })) - } - return result - } -} - -async function getInterface (resource, transaction) { - switch (resource.interfaceProtocol) { - case 'http': - case 'https': - case 'ws': - case 'wss': { - return HTTPBasedResourceInterfaceManager.findOneWithEndpoints({ id: resource.interfaceId }, transaction) - } - default: - break - } -} - -async function _createHttpBasedInterfaceEndpoints (endpoints, interfaceId, transaction) { - for (const endpoint of (endpoints || [])) { - await HTTPBasedResourceInterfaceEndpointsManager.create({ ...endpoint, interfaceId }, transaction) - } -} - -async function _createHttpBasedInterface ({ endpoints }, edgeResourceId, transaction) { - const httpBasedInterface = await HTTPBasedResourceInterfaceManager.create({ edgeResourceId }, transaction) - await _createHttpBasedInterfaceEndpoints(endpoints, httpBasedInterface.id, transaction) - return httpBasedInterface -} - -async function _updateHttpBasedInterface (id, { endpoints }, transaction) { - const httpBasedInterface = await HTTPBasedResourceInterfaceManager.findOne({ id }, transaction) - if (httpBasedInterface) { - await HTTPBasedResourceInterfaceEndpointsManager.delete({ interfaceId: httpBasedInterface.id }, transaction) - await _createHttpBasedInterfaceEndpoints(endpoints, id, transaction) - } -} - -async function _createInterface (resourceData, resourceId, transaction) { - switch (resourceData.interfaceProtocol) { - case 'http': - case 'https': - case 'ws': - case 'wss': { - return _createHttpBasedInterface(resourceData.interface, resourceId, transaction) - } - default: - break - } -} - -async function _deleteInterface (resourceData, transaction) { - switch (resourceData.interfaceProtocol) { - case 'http': - case 'https': - case 'ws': - case 'wss': { - return HTTPBasedResourceInterfaceManager.delete({ id: resourceData.interfaceId }, transaction) - } - default: - break - } -} - -async function _updateInterface (resource, newData, transaction) { - if (resource.interfaceProtocol && newData.interface) { - switch (resource.interfaceProtocol) { - case 'http': - case 'https': - case 'ws': - case 'wss': { - return _updateHttpBasedInterface(resource.interfaceId, newData.interface, transaction) - } - default: - break - } - } -} - -async function _createOrchestrationTags (tagArray, edgeResourceModel, transaction) { - if (tagArray) { - let tags = [] - for (const tag of tagArray) { - let tagModel = await TagsManager.findOne({ value: tag }, transaction) - if (!tagModel) { - tagModel = await TagsManager.create({ value: tag }, transaction) - } - tags.push(tagModel) - } - await edgeResourceModel.setOrchestrationTags(tags) - return tags - } - return [] -} - -async function _updateOrchestrationTags (tagArray, edgeResourceModel, transaction) { - const oldTags = await edgeResourceModel.getOrchestrationTags() - const linkedAgents = await edgeResourceModel.getAgents() - await edgeResourceModel.setOrchestrationTags([]) - const newTags = await _createOrchestrationTags(tagArray, edgeResourceModel, transaction) - for (const agent of linkedAgents) { - await agent.removeTags(oldTags) - await agent.addTags(newTags) - await ChangeTrackingService.update(agent.uuid, ChangeTrackingService.events.edgeResources, transaction) - } -} - -async function createEdgeResource (edgeResourceData, transaction) { - await Validator.validate(edgeResourceData, Validator.schemas.edgeResourceCreate) - const { name, description, version, orchestrationTags, interfaceProtocol, display, custom } = edgeResourceData - const existingResource = await EdgeResourceManager.findOne({ name, version }, transaction) - if (existingResource) { - throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.DUPLICATE_RESOURCE_NAME_VERSION, name, version)) - } - const resourceData = { - name, - description, - orchestrationTags, - interfaceProtocol, - version - } - if (display) { - resourceData.displayName = display.name - resourceData.displayIcon = display.icon - resourceData.displayColor = display.color - } - if (custom) { - resourceData.custom = JSON.stringify(custom) - } - const resource = await EdgeResourceManager.create(resourceData, transaction) - try { - if (orchestrationTags) { - await _createOrchestrationTags(orchestrationTags, resource, transaction) - } - resource.interfaceId = (await _createInterface(edgeResourceData, resource.id, transaction)).id - await resource.save(transaction) - } catch (e) { - // Clean up - await EdgeResourceManager.delete({ id: resource.id }, transaction) - throw e - } - return buildGetObject(resource) -} - -async function updateEdgeResourceEndpoint (edgeResourceData, { name: oldName, version }, transaction) { - await Validator.validate(edgeResourceData, Validator.schemas.edgeResourceUpdate) - const oldData = await EdgeResourceManager.findOne({ name: oldName, version }, transaction) - if (!oldData) { - if (!edgeResourceData.name) { - edgeResourceData.name = oldName - } - if (!edgeResourceData.version) { - edgeResourceData.version = version - } - return createEdgeResource(edgeResourceData, transaction) - } - if (edgeResourceData.version && oldData.version !== edgeResourceData.version) { - throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.RESOURCE_UPDATE_VERSION_MISMATCH)) - } - const { name, description, orchestrationTags, interfaceProtocol, display, custom } = edgeResourceData - const newData = { - name, - description, - orchestrationTags, - interfaceProtocol - } - if (display) { - newData.displayName = display.name - newData.displayIcon = display.icon - newData.displayColor = display.color - } - if (custom) { - newData.custom = JSON.stringify(custom) - } - AppHelper.deleteUndefinedFields(newData) - if (newData.name && newData.name !== oldData.name) { - const newVersion = newData.version ? newData.version : version - const existingResource = await EdgeResourceManager.findOne({ name, version: newVersion }, transaction) - if (existingResource) { - throw new Errors.DuplicatePropertyError(AppHelper.formatMessage(ErrorMessages.DUPLICATE_RESOURCE_NAME_VERSION, name, newVersion)) - } - } - - await EdgeResourceManager.update({ name: oldName, version }, newData, transaction) - - if (orchestrationTags) { - await _updateOrchestrationTags(orchestrationTags, oldData, transaction) - } - if (newData.interfaceProtocol && (oldData.interfaceProtocol !== newData.interfaceProtocol)) { - await _deleteInterface(oldData, transaction) - await _createInterface(edgeResourceData, oldData.id, transaction) - } else { - await _updateInterface(oldData, edgeResourceData, transaction) - } -} - -async function deleteEdgeResource ({ name, version }, transaction) { - const resource = await EdgeResourceManager.findOne({ name, version }, transaction) - if (!resource) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.NOT_FOUND_RESOURCE_NAME_VERSION, name, version)) - } - const agents = await resource.getAgents() - const tags = await resource.getOrchestrationTags() - for (const agent of agents) { - await agent.removeTags(tags) - await ChangeTrackingService.update(agent.uuid, ChangeTrackingService.events.edgeResources, transaction) - } - await EdgeResourceManager.delete({ name, version }, transaction) -} - -async function linkEdgeResource ({ name, version }, uuid, transaction) { - const resource = await EdgeResourceManager.findOne({ name, version }, transaction) - if (!resource) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.NOT_FOUND_RESOURCE_NAME_VERSION, name, version)) - } - const agent = await FogManager.findOne({ uuid }, transaction) - if (!agent) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.NOT_FOUND_AGENT_NAME, uuid)) - } - - await agent.addEdgeResource(resource.id, transaction) - const tags = await resource.getOrchestrationTags() - await agent.addTags(tags) - await ChangeTrackingService.update(agent.uuid, ChangeTrackingService.events.edgeResources, transaction) -} - -async function unlinkEdgeResource ({ name, version }, uuid, transaction) { - const resource = await EdgeResourceManager.findOne({ name, version }, transaction) - if (!resource) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.NOT_FOUND_RESOURCE_NAME_VERSION, name, version)) - } - const agent = await FogManager.findOne({ uuid }, transaction) - if (!agent) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.NOT_FOUND_AGENT_NAME, uuid)) - } - - await agent.removeEdgeResource(resource.id, transaction) - const tags = await resource.getOrchestrationTags() - await agent.removeTags(tags) - await ChangeTrackingService.update(agent.uuid, ChangeTrackingService.events.edgeResources, transaction) -} - -function buildGetObject (resource) { - const { name, id, interfaceProtocol, description, version, displayName, displayIcon, displayColor, orchestrationTags, custom } = resource - return { - id, - name, - description, - version, - interfaceProtocol, - display: { - name: displayName, - icon: displayIcon, - color: displayColor - }, - custom: JSON.parse(custom || '{}'), - orchestrationTags: (orchestrationTags || []).map(t => t.value), - interface: resource.interface - } -} - -module.exports = { - getEdgeResource: TransactionDecorator.generateTransaction(getEdgeResource), - listEdgeResources: TransactionDecorator.generateTransaction(listEdgeResources), - createEdgeResource: TransactionDecorator.generateTransaction(createEdgeResource), - deleteEdgeResource: TransactionDecorator.generateTransaction(deleteEdgeResource), - updateEdgeResourceEndpoint: TransactionDecorator.generateTransaction(updateEdgeResourceEndpoint), - linkEdgeResource: TransactionDecorator.generateTransaction(linkEdgeResource), - unlinkEdgeResource: TransactionDecorator.generateTransaction(unlinkEdgeResource), - getInterface, - buildGetObject -} diff --git a/src/services/iofog-service.js b/src/services/iofog-service.js index ffe794c8..a5bc918a 100644 --- a/src/services/iofog-service.js +++ b/src/services/iofog-service.js @@ -31,7 +31,6 @@ const MicroserviceManager = require('../data/managers/microservice-manager') const ApplicationManager = require('../data/managers/application-manager') const TagsManager = require('../data/managers/tags-manager') const MicroserviceService = require('./microservices-service') -const EdgeResourceService = require('./edge-resource-service') const RouterManager = require('../data/managers/router-manager') const MicroserviceExtraHostManager = require('../data/managers/microservice-extra-host-manager') const MicroserviceStatusManager = require('../data/managers/microservice-status-manager') @@ -921,20 +920,6 @@ async function _getFogNatsConfig (fog, transaction) { return natsConfig } -async function _getFogEdgeResources (fog, transaction) { - const resourceAttributes = [ - 'name', - 'version', - 'description', - 'interfaceProtocol', - 'displayName', - 'displayIcon', - 'displayColor' - ] - const resources = await fog.getEdgeResources({ attributes: resourceAttributes }) - return resources.map(EdgeResourceService.buildGetObject) -} - async function _getFogVolumeMounts (fog, transaction) { const volumeMountAttributes = [ 'name', @@ -956,7 +941,6 @@ async function _getFogVolumeMounts (fog, transaction) { async function _getFogExtraInformation (fog, transaction) { const routerConfig = await _getFogRouterConfig(fog, transaction) const natsConfig = await _getFogNatsConfig(fog, transaction) - const edgeResources = await _getFogEdgeResources(fog, transaction) const volumeMounts = await _getFogVolumeMounts(fog, transaction) // Transform to plain JS object if (fog.toJSON && typeof fog.toJSON === 'function') { @@ -970,7 +954,7 @@ async function _getFogExtraInformation (fog, transaction) { image: architecture.image, description: architecture.description } : undefined - return { ...fogFields, archId, arch, tags: _mapTags(fog), ...routerConfig, ...natsConfig, edgeResources, volumeMounts } + return { ...fogFields, archId, arch, tags: _mapTags(fog), ...routerConfig, ...natsConfig, volumeMounts } } // Map tags to string array From 9c6be8f79302131d3564af60d151b62809b111b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 04:17:44 +0300 Subject: [PATCH 14/75] Remove diagnostics, strace, and image snapshot support. Delete diagnostics CLI and HTTP stack, strace managers/models, config keys, and v3.8.0 migration columns for removed change-tracking fields. --- scripts/cli-tests.js | 55 ---- src/cli/diagnostics.js | 184 ------------- src/cli/index.js | 6 +- src/config/config.yaml | 5 - src/config/env-mapping.js | 3 - src/controllers/diagnostic-controller.js | 42 --- src/data/managers/iofog-manager.js | 19 -- src/data/managers/microservice-manager.js | 14 - .../managers/strace-diagnostics-manager.js | 25 -- src/data/managers/strace-manager.js | 57 ---- .../mysql/db_migration_mysql_v3.8.0.sql | 3 - .../postgres/db_migration_pg_v3.8.0.sql | 3 - .../sqlite/db_migration_sqlite_v3.8.0.sql | 3 - src/data/models/changetracking.js | 10 - src/data/models/microservice.js | 10 - src/data/models/stracediagnostics.js | 37 --- src/helpers/constants.js | 6 - src/helpers/error-messages.js | 9 - src/routes/diagnostics.js | 218 ---------------- src/schemas/agent.js | 28 +- src/schemas/diagnostics.js | 48 ---- src/schemas/microservice.js | 1 - src/services/diagnostic-service.js | 243 ------------------ 23 files changed, 3 insertions(+), 1026 deletions(-) delete mode 100644 src/cli/diagnostics.js delete mode 100644 src/controllers/diagnostic-controller.js delete mode 100644 src/data/managers/strace-diagnostics-manager.js delete mode 100644 src/data/managers/strace-manager.js delete mode 100644 src/data/models/stracediagnostics.js delete mode 100644 src/routes/diagnostics.js delete mode 100644 src/schemas/diagnostics.js delete mode 100644 src/services/diagnostic-service.js diff --git a/scripts/cli-tests.js b/scripts/cli-tests.js index 564a5946..72bb7c34 100644 --- a/scripts/cli-tests.js +++ b/scripts/cli-tests.js @@ -254,60 +254,6 @@ function testRegistrySection () { } } -function testDiagnosticsSection () { - console.log('\n=============================\nStarting diagnostics section..') - - const registryCreateResponse = responseHasFields(executeCommand('registry add -U testRegistryUri -b -l testUserName' + - ' -p testPassword -e testEmail@gmail.com -u '), registryCreateFields) - const registryId = registryCreateResponse.id - - const catalogCreateResponse = responseHasFields(executeCommand('catalog add -n testCatalogItem1 -d testDescription' + - ' -c testCategory -x testIntelImage -a testArmImage -p testPublisher -s 15 -r 15 -t testPicture -g ' + - registryId + ' -I testInputType -F testInputFormat -O testOutputType -T testOutputFormat ' + - '-X \'{}\' -u '), catalogCreateFields) - const catalogId = catalogCreateResponse.id - - const applicationCreateResponse = responseHasFields(executeCommand('application add -n test-application1 -d testDescription' + - ' -a -u '), applicationCreateFields) - const applicationId = applicationCreateResponse.name - - const ioFogCreateResponse = responseHasFields(executeCommand('iofog add -n ioFog3 -l testLocation -t 55 -g 65' + - ' -d testDescription -D testDockerUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + - ' -s 25 -F 27 -Q 26 -B -W -A -y 1 -u '), ioFogCreateFields) - const ioFogUuid = ioFogCreateResponse.uuid - - const microserviceCreateResponse = responseHasFields(executeCommand('microservice add -n microservice-name-1' + - ' -c ' + catalogId + ' -F ' + applicationId + ' -I ' + ioFogUuid + ' -g \'{}\' -v /host_src:/container_src:rw -l 15 -R' + - ' -p 80:8080:false -u '), microserviceCreateFields) - const microserviceUuid = microserviceCreateResponse.uuid - - try { - responseEquals(testCommand('diagnostics strace-update -e -i ' + microserviceUuid), - 'Microservice strace has been enabled') - responseContains(testCommand('diagnostics strace-info -f string -i ' + microserviceUuid), - 'Microservice strace data has been retrieved successfully.') - responseContains(testCommand('diagnostics strace-ftp-post -i ' + microserviceUuid + ' -h ftpTestHost -p 2024' + - ' -u testFtpUser -s testFtpPass -d ftpTestDestination'), 'FTP error') - responseContains(testCommand('diagnostics image-snapshot-create -i ' + microserviceUuid), - 'Microservice image snapshot has been created successfully.') - responseContains(testCommand('diagnostics image-snapshot-get -i ' + microserviceUuid), - 'Image snapshot is not available for this microservice.') - executeCommand('microservice remove -i ' + microserviceUuid) - executeCommand('iofog remove -i ' + ioFogUuid) - executeCommand('application remove -i ' + applicationId) - executeCommand('catalog remove -i ' + catalogId) - executeCommand('registry remove -i ' + registryId) - executeCommand('user remove -e diagnosticsUser@domain.com') - } catch (exception) { - executeCommand('microservice remove -i ' + microserviceUuid) - executeCommand('iofog remove -i ' + ioFogUuid) - executeCommand('application remove -i ' + applicationId) - executeCommand('catalog remove -i ' + catalogId) - executeCommand('registry remove -i ' + registryId) - executeCommand('user remove -e diagnosticsUser@domain.com') - } -} - function testCommand (command) { console.log('\n Testing command \'' + command + '\'') testsCounter++ @@ -386,7 +332,6 @@ async function cliTest () { testApplicationSection() testMicroserviceSection() testRegistrySection() - testDiagnosticsSection() restoreDBs() } catch (exception) { diff --git a/src/cli/diagnostics.js b/src/cli/diagnostics.js deleted file mode 100644 index 165c55ed..00000000 --- a/src/cli/diagnostics.js +++ /dev/null @@ -1,184 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const BaseCLIHandler = require('./base-cli-handler') -const constants = require('../helpers/constants') -const logger = require('../logger') -const DiagnosticService = require('../services/diagnostic-service') -const AppHelper = require('../helpers/app-helper') -const CliDataTypes = require('./cli-data-types') - -class Diagnostics extends BaseCLIHandler { - constructor () { - super() - - this.name = constants.CMD_DIAGNOSTICS - this.commandDefinitions = [ - { - name: 'command', - defaultOption: true, - group: constants.CMD - }, - { - name: 'enable', - alias: 'e', - type: Boolean, - description: 'Enable microservice strace', - group: [constants.CMD_STRACE_UPDATE] - }, - { - name: 'disable', - alias: 'o', - type: Boolean, - description: 'Disable microservice strace', - group: [constants.CMD_STRACE_UPDATE] - }, - { - name: 'microservice-uuid', - alias: 'i', - type: String, - description: 'Microservice UUID', - group: [constants.CMD_STRACE_UPDATE, constants.CMD_STRACE_INFO, constants.CMD_STRACE_FTP_POST, - constants.CMD_IMAGE_SNAPSHOT_CREATE, constants.CMD_IMAGE_SNAPSHOT_GET] - }, - { - name: 'format', - alias: 'f', - type: String, - description: 'Format of strace data to receive. Possible values: string, file', - group: [constants.CMD_STRACE_INFO] - }, - { - name: 'ftpHost', - alias: 'h', - type: String, - description: 'FTP host', - group: [constants.CMD_STRACE_FTP_POST] - }, - { - name: 'ftpPort', - alias: 'p', - type: CliDataTypes.Integer, - description: 'FTP port', - group: [constants.CMD_STRACE_FTP_POST] - }, - { - name: 'ftpUser', - alias: 'u', - type: String, - description: 'FTP user', - group: [constants.CMD_STRACE_FTP_POST] - }, - { - name: 'ftpPass', - alias: 's', - type: String, - description: 'FTP user password', - group: [constants.CMD_STRACE_FTP_POST] - }, - { - name: 'ftpDestDir', - alias: 'd', - type: String, - description: 'FTP destination directory', - group: [constants.CMD_STRACE_FTP_POST] - } - ] - this.commands = { - [constants.CMD_STRACE_UPDATE]: 'Change microservice strace status to enabled or disabled.', - [constants.CMD_STRACE_INFO]: 'Get microservice strace data.', - [constants.CMD_STRACE_FTP_POST]: 'Post microservice strace data to ftp.', - [constants.CMD_IMAGE_SNAPSHOT_CREATE]: 'Create microservice image snapshot.', - [constants.CMD_IMAGE_SNAPSHOT_GET]: 'Get microservice image snapshot.' - } - } - - async run (args) { - try { - const diagnosticCommand = this.parseCommandLineArgs(this.commandDefinitions, { argv: args.argv, partial: false }) - - const command = diagnosticCommand.command.command - - this.validateParameters(command, this.commandDefinitions, args.argv) - - switch (command) { - case constants.CMD_STRACE_UPDATE: - await _executeCase(diagnosticCommand, constants.CMD_STRACE_UPDATE, _changeMicroserviceStraceState) - break - case constants.CMD_STRACE_INFO: - await _executeCase(diagnosticCommand, constants.CMD_STRACE_INFO, _getMicroserviceStraceData) - break - case constants.CMD_STRACE_FTP_POST: - await _executeCase(diagnosticCommand, constants.CMD_STRACE_FTP_POST, _postMicroserviceStraceDataToFtp) - break - case constants.CMD_IMAGE_SNAPSHOT_CREATE: - await _executeCase(diagnosticCommand, constants.CMD_IMAGE_SNAPSHOT_CREATE, _postMicroserviceImageSnapshotCreate) - break - case constants.CMD_IMAGE_SNAPSHOT_GET: - await _executeCase(diagnosticCommand, constants.CMD_IMAGE_SNAPSHOT_GET, _getMicroserviceImageSnapshot) - break - case constants.CMD_HELP: - default: - return this.help([]) - } - } catch (error) { - this.handleCLIError(error, args.argv) - } - } -} - -const _executeCase = async function (diagnosticCommand, commandName, f) { - try { - const item = diagnosticCommand[commandName] - await f(item) - } catch (error) { - logger.error(error.message) - } -} - -const _changeMicroserviceStraceState = async function (obj) { - const isEnable = AppHelper.validateBooleanCliOptions(obj.enable, obj.disable) - logger.cliReq('diagnostics strace-update', { args: { isEnable: isEnable } }) - await DiagnosticService.changeMicroserviceStraceState(obj.microserviceUuid, { enable: isEnable }, {}, true) - const msg = isEnable ? 'Microservice strace has been enabled' : 'Microservice strace has been disabled' - logger.cliRes(msg) -} - -const _getMicroserviceStraceData = async function (obj) { - logger.cliReq('diagnostics strace-info', { args: obj }) - const result = await DiagnosticService.getMicroserviceStraceData(obj.microserviceUuid, { format: obj.format }, {}, true) - logger.cliRes('Strace data:') - logger.cliRes('=============================') - logger.cliRes(result.data) - logger.cliRes('Microservice strace data has been retrieved successfully.') -} - -const _postMicroserviceStraceDataToFtp = async function (obj) { - logger.cliReq('diagnostics strace-ftp-post', { args: obj }) - await DiagnosticService.postMicroserviceStraceDatatoFtp(obj.microserviceUuid, obj, {}, true) - logger.cliRes('Strace data has been posted to FTP successfully.') -} - -const _postMicroserviceImageSnapshotCreate = async function (obj) { - logger.cliReq('diagnostics image-snapshot-create') - await DiagnosticService.postMicroserviceImageSnapshotCreate(obj.microserviceUuid, {}, true) - logger.cliRes('Microservice image snapshot has been created successfully.') -} - -const _getMicroserviceImageSnapshot = async function (obj) { - logger.cliReq('diagnostics image-snapshot-get') - const filePath = await DiagnosticService.getMicroserviceImageSnapshot(obj.microserviceUuid, {}, true) - logger.cliRes('Microservice images path = ' + filePath) -} - -module.exports = new Diagnostics() diff --git a/src/cli/index.js b/src/cli/index.js index a905bf1d..97f3596b 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -21,7 +21,6 @@ const Application = require('./application') const Microservice = require('./microservice') const Registry = require('./registry') const Controller = require('./controller') -const Diagnostics = require('./diagnostics') const constants = require('../helpers/constants') class Cli extends BaseCLIHandler { @@ -43,8 +42,7 @@ class Cli extends BaseCLIHandler { [constants.CMD_CATALOG]: 'Microservices catalog operations.', [constants.CMD_FLOW]: 'Application operations.', [constants.CMD_MICROSERVICE]: 'Microservice instance operations.', - [constants.CMD_REGISTRY]: 'Registries instance operations.', - [constants.CMD_DIAGNOSTICS]: 'Diagnostic instance operations.' + [constants.CMD_REGISTRY]: 'Registries instance operations.' } } @@ -77,8 +75,6 @@ class Cli extends BaseCLIHandler { return Microservice.run({ argv }) case constants.CMD_REGISTRY: return Registry.run({ argv }) - case constants.CMD_DIAGNOSTICS: - return Diagnostics.run({ argv }) case constants.CMD_HELP: default: return this.help([], false) diff --git a/src/config/config.yaml b/src/config/config.yaml index b6388415..b93d990f 100644 --- a/src/config/config.yaml +++ b/src/config/config.yaml @@ -124,11 +124,6 @@ systemImages: nats: enabled: true -# Diagnostics Configuration -diagnostics: - directory: "diagnostic" # Diagnostics directory - - # Vault Configuration (for compliance with NIST, ISO 27001, NATO, IEC 62443) # When enabled, sensitive data (secrets, certificates, private keys) will be stored # in the configured vault provider instead of encrypted in the database diff --git a/src/config/env-mapping.js b/src/config/env-mapping.js index df23535d..9890d5f4 100644 --- a/src/config/env-mapping.js +++ b/src/config/env-mapping.js @@ -97,9 +97,6 @@ module.exports = { // NATS Configuration 'NATS_ENABLED': 'nats.enabled', - // Diagnostics Configuration - 'DIAGNOSTICS_DIRECTORY': 'diagnostics.directory', - // Vault Configuration 'VAULT_ENABLED': 'vault.enabled', 'VAULT_PROVIDER': 'vault.provider', diff --git a/src/controllers/diagnostic-controller.js b/src/controllers/diagnostic-controller.js deleted file mode 100644 index 35d053be..00000000 --- a/src/controllers/diagnostic-controller.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const DiagnosticService = require('../services/diagnostic-service') - -const changeMicroserviceStraceStateEndPoint = async function (req) { - return DiagnosticService.changeMicroserviceStraceState(req.params.uuid, req.body, false) -} - -const getMicroserviceStraceDataEndPoint = async function (req) { - return DiagnosticService.getMicroserviceStraceData(req.params.uuid, req.query, false) -} - -const postMicroserviceStraceDataToFtpEndPoint = async function (req) { - return DiagnosticService.postMicroserviceStraceDatatoFtp(req.params.uuid, req.body, false) -} - -const createMicroserviceImageSnapshotEndPoint = async function (req) { - return DiagnosticService.postMicroserviceImageSnapshotCreate(req.params.uuid, false) -} - -const getMicroserviceImageSnapshotEndPoint = async function (req) { - return DiagnosticService.getMicroserviceImageSnapshot(req.params.uuid, false) -} - -module.exports = { - changeMicroserviceStraceStateEndPoint: (changeMicroserviceStraceStateEndPoint), - getMicroserviceStraceDataEndPoint: (getMicroserviceStraceDataEndPoint), - postMicroserviceStraceDataToFtpEndPoint: (postMicroserviceStraceDataToFtpEndPoint), - createMicroserviceImageSnapshotEndPoint: (createMicroserviceImageSnapshotEndPoint), - getMicroserviceImageSnapshotEndPoint: (getMicroserviceImageSnapshotEndPoint) -} diff --git a/src/data/managers/iofog-manager.js b/src/data/managers/iofog-manager.js index 3e8a32e7..ea03b45b 100644 --- a/src/data/managers/iofog-manager.js +++ b/src/data/managers/iofog-manager.js @@ -17,8 +17,6 @@ const models = require('../models') const Fog = models.Fog const Tags = models.Tags const Architecture = models.Architecture -const Microservice = models.Microservice -const Strace = models.StraceDiagnostics class FogManager extends BaseManager { getEntity () { @@ -83,23 +81,6 @@ class FogManager extends BaseManager { } }) } - - findFogStraces (where, transaction) { - return Fog.findOne({ - include: [ - { - model: Microservice, - as: 'microservice', - required: true, - include: [{ - model: Strace, - as: 'strace', - required: true - }] - }], - where: where - }, { transaction: transaction }) - } } const instance = new FogManager() diff --git a/src/data/managers/microservice-manager.js b/src/data/managers/microservice-manager.js index 80c0f5d6..c2fd4ba6 100644 --- a/src/data/managers/microservice-manager.js +++ b/src/data/managers/microservice-manager.js @@ -22,7 +22,6 @@ const MicroserviceCdiDev = models.MicroserviceCdiDev const MicroserviceCapAdd = models.MicroserviceCapAdd const MicroserviceCapDrop = models.MicroserviceCapDrop const VolumeMapping = models.VolumeMapping -const StraceDiagnostics = models.StraceDiagnostics const CatalogItem = models.CatalogItem const CatalogItemImage = models.CatalogItemImage const Fog = models.Fog @@ -41,7 +40,6 @@ const microserviceExcludedFields = [ 'updatedBy', 'rebuild', 'deleteWithCleanUp', - 'imageSnapshot', 'catalog_item_id', 'iofog_uuid' ] @@ -101,12 +99,6 @@ class MicroserviceManager extends BaseManager { required: false, attributes: ['hostDestination', 'containerDestination', 'accessMode', 'type'] }, - { - model: StraceDiagnostics, - as: 'strace', - required: false, - attributes: ['straceRun'] - }, { model: CatalogItemImage, as: 'images', @@ -323,12 +315,6 @@ class MicroserviceManager extends BaseManager { required: false, attributes: ['hostDestination', 'containerDestination', 'accessMode', 'type'] }, - { - model: StraceDiagnostics, - as: 'strace', - required: false, - attributes: ['straceRun'] - }, { model: CatalogItemImage, as: 'images', diff --git a/src/data/managers/strace-diagnostics-manager.js b/src/data/managers/strace-diagnostics-manager.js deleted file mode 100644 index 9b2402ba..00000000 --- a/src/data/managers/strace-diagnostics-manager.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const BaseManager = require('./base-manager') -const models = require('../models') -const StraceDiagnostics = models.StraceDiagnostics - -class StraceDiagnosticsManager extends BaseManager { - getEntity () { - return StraceDiagnostics - } -} - -const instance = new StraceDiagnosticsManager() -module.exports = instance diff --git a/src/data/managers/strace-manager.js b/src/data/managers/strace-manager.js deleted file mode 100644 index 577cbac1..00000000 --- a/src/data/managers/strace-manager.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const BaseManager = require('./base-manager') -const models = require('../models') -const Strace = models.StraceDiagnostics - -const Errors = require('../../helpers/errors') -const ErrorMessages = require('../../helpers/error-messages') -const AppHelper = require('../../helpers/app-helper') - -const maxBufferSize = 1e8 - -class StraceManager extends BaseManager { - getEntity () { - return Strace - } - - async pushBufferByMicroserviceUuid (uuid, pushingData, transaction) { - const strace = await this.findOne({ - microserviceUuid: uuid - }, transaction) - if (!strace) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, uuid)) - } - - const newBuffer = this._updateBuffer(strace.buffer, pushingData) - return this.update({ - microserviceUuid: uuid - }, { - buffer: newBuffer - }, transaction) - } - - _updateBuffer (oldBuf, pushingData) { - let newBuffer = oldBuf + pushingData - const delta = newBuffer.length - maxBufferSize - if (delta > 0) { - newBuffer = '[ioFogController Info] Buffer size is limited, so some of previous data was lost \n' + - newBuffer.substring(delta) - } - return newBuffer - }; -} - -const instance = new StraceManager() -module.exports = instance diff --git a/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql b/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql index adf95e7a..e6d7b4c8 100644 --- a/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql +++ b/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql @@ -142,9 +142,7 @@ CREATE TABLE IF NOT EXISTS ChangeTrackings ( config BOOLEAN DEFAULT false, registries BOOLEAN DEFAULT false, tunnel BOOLEAN DEFAULT false, - diagnostics BOOLEAN DEFAULT false, router_changed BOOLEAN DEFAULT false, - image_snapshot BOOLEAN DEFAULT false, prune BOOLEAN DEFAULT false, last_updated VARCHAR(255) DEFAULT false, iofog_uuid VARCHAR(36), @@ -221,7 +219,6 @@ CREATE TABLE IF NOT EXISTS Microservices ( rebuild BOOLEAN DEFAULT false, root_host_access BOOLEAN DEFAULT false, log_size BIGINT DEFAULT 0, - image_snapshot VARCHAR(255) DEFAULT '', `delete` BOOLEAN DEFAULT false, delete_with_cleanup BOOLEAN DEFAULT false, created_at DATETIME, diff --git a/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql b/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql index 6fa0fdec..1db1b169 100644 --- a/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql +++ b/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql @@ -140,9 +140,7 @@ CREATE TABLE IF NOT EXISTS "ChangeTrackings" ( config BOOLEAN DEFAULT false, registries BOOLEAN DEFAULT false, tunnel BOOLEAN DEFAULT false, - diagnostics BOOLEAN DEFAULT false, router_changed BOOLEAN DEFAULT false, - image_snapshot BOOLEAN DEFAULT false, prune BOOLEAN DEFAULT false, last_updated VARCHAR(255) DEFAULT false, iofog_uuid VARCHAR(36), @@ -219,7 +217,6 @@ CREATE TABLE IF NOT EXISTS "Microservices" ( rebuild BOOLEAN DEFAULT false, root_host_access BOOLEAN DEFAULT false, log_size BIGINT DEFAULT 0, - image_snapshot VARCHAR(255) DEFAULT '', delete BOOLEAN DEFAULT false, delete_with_cleanup BOOLEAN DEFAULT false, created_at TIMESTAMP(0), diff --git a/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql b/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql index 2f141d98..8ea641f3 100644 --- a/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql +++ b/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql @@ -140,9 +140,7 @@ CREATE TABLE IF NOT EXISTS ChangeTrackings ( config BOOLEAN DEFAULT false, registries BOOLEAN DEFAULT false, tunnel BOOLEAN DEFAULT false, - diagnostics BOOLEAN DEFAULT false, router_changed BOOLEAN DEFAULT false, - image_snapshot BOOLEAN DEFAULT false, prune BOOLEAN DEFAULT false, last_updated VARCHAR(255) DEFAULT false, iofog_uuid VARCHAR(36), @@ -219,7 +217,6 @@ CREATE TABLE IF NOT EXISTS Microservices ( rebuild BOOLEAN DEFAULT false, root_host_access BOOLEAN DEFAULT false, log_size BIGINT DEFAULT 0, - image_snapshot VARCHAR(255) DEFAULT '', `delete` BOOLEAN DEFAULT false, delete_with_cleanup BOOLEAN DEFAULT false, created_at DATETIME, diff --git a/src/data/models/changetracking.js b/src/data/models/changetracking.js index 77af5d07..9b071f95 100644 --- a/src/data/models/changetracking.js +++ b/src/data/models/changetracking.js @@ -61,21 +61,11 @@ module.exports = (sequelize, DataTypes) => { field: 'tunnel', defaultValue: false }, - diagnostics: { - type: DataTypes.BOOLEAN, - field: 'diagnostics', - defaultValue: false - }, routerChanged: { type: DataTypes.BOOLEAN, field: 'router_changed', defaultValue: false }, - isImageSnapshot: { - type: DataTypes.BOOLEAN, - field: 'image_snapshot', - defaultValue: false - }, prune: { type: DataTypes.BOOLEAN, field: 'prune', diff --git a/src/data/models/microservice.js b/src/data/models/microservice.js index 39f4b156..27a121f9 100644 --- a/src/data/models/microservice.js +++ b/src/data/models/microservice.js @@ -94,11 +94,6 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.FLOAT, field: 'memory_limit' }, - imageSnapshot: { - type: DataTypes.TEXT, - field: 'image_snapshot', - defaultValue: '' - }, execEnabled: { type: DataTypes.BOOLEAN, field: 'exec_enabled', @@ -207,11 +202,6 @@ module.exports = (sequelize, DataTypes) => { as: 'volumeMappings' }) - Microservice.hasOne(models.StraceDiagnostics, { - foreignKey: 'microservice_uuid', - as: 'strace' - }) - Microservice.hasOne(models.MicroserviceStatus, { foreignKey: 'microservice_uuid', as: 'microserviceStatus' diff --git a/src/data/models/stracediagnostics.js b/src/data/models/stracediagnostics.js deleted file mode 100644 index d8771038..00000000 --- a/src/data/models/stracediagnostics.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict' -module.exports = (sequelize, DataTypes) => { - const StraceDiagnostics = sequelize.define('StraceDiagnostics', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - allowNull: false, - field: 'id' - }, - straceRun: { - type: DataTypes.BOOLEAN, - field: 'strace_run' - }, - buffer: { - type: DataTypes.TEXT, - field: 'buffer', - defaultValue: '' - } - }, { - tableName: 'StraceDiagnostics', - timestamps: false, - underscored: true - }) - StraceDiagnostics.associate = function (models) { - StraceDiagnostics.belongsTo(models.Microservice, { - foreignKey: { - name: 'microserviceUuid', - field: 'microservice_uuid' - }, - as: 'microservice', - onDelete: 'cascade' - - }) - } - return StraceDiagnostics -} diff --git a/src/helpers/constants.js b/src/helpers/constants.js index d3a3ad54..06ef4b64 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -49,12 +49,6 @@ module.exports = { CMD_CONTROLLER: 'controller', CMD_EMAIL_ACTIVATION: 'email-activation', CMD_ARCHITECTURES: 'architectures', - CMD_DIAGNOSTICS: 'diagnostics', - CMD_STRACE_UPDATE: 'strace-update', - CMD_STRACE_INFO: 'strace-info', - CMD_STRACE_FTP_POST: 'strace-ftp-post', - CMD_IMAGE_SNAPSHOT_CREATE: 'image-snapshot-create', - CMD_IMAGE_SNAPSHOT_GET: 'image-snapshot-get', CMD_HAL_HW: 'hal-hw', CMD_HAL_USB: 'hal-usb', CMD_IOFOG_PRUNE: 'prune', diff --git a/src/helpers/error-messages.js b/src/helpers/error-messages.js index 642120dd..47bdf3b8 100644 --- a/src/helpers/error-messages.js +++ b/src/helpers/error-messages.js @@ -44,22 +44,15 @@ module.exports = { INVALID_PORT_FORMAT: 'Invalid port format', INVALID_FILE_PATH: 'Invalid file path', PORT_NOT_AVAILABLE: 'Port \'{}\' is not available', - UNABLE_TO_WRITE_STRACE: 'Error while writing strace data to file. File name: \'{}\', err: \'{}\'', - UNABLE_TO_DELETE_STRACE: 'Error while deleting strace data file. File name: \'{}\', err: \'{}\'', - FTP_ERROR: 'FTP error: \'{}\'', EXPIRED_PROVISION_KEY: 'Expired provision key', VERSION_COMMAND_NOT_FOUND: 'Version command not found', - STRACE_WITHOUT_FOG: 'Can\'t run strace for microservice without ioFog.', INVALID_ACTION_PROPERTY: 'Unknown action property. Action can be "open" or "close"', - IMAGE_SNAPSHOT_NOT_FOUND: 'Image snapshot not found', INVALID_MICROSERVICES_FOG_TYPE: 'Some of microservices haven\'t proper docker images for this ioFog type. ' + 'List of invalid microservices:\n', INVALID_MICROSERVICE_CONFIG: 'Can\'t create network microservice without appropriate configuration.', INVALID_MICROSERVICE_USER: 'Invalid microservice user or UUID', APPLICATION_NOT_ACTIVATED: 'Application {} is not activated', ROUTE_NOT_FOUND: 'Route not found', - IMAGE_SNAPSHOT_WITHOUT_FOG: 'Can not run image snapshot for microservice without ioFog.', - IMAGE_SNAPSHOT_NOT_AVAILABLE: 'Image snapshot is not available for this microservice.', FILE_DOES_NOT_EXIST: 'File does not exist.', MICROSERVICE_DOES_NOT_HAVE_IMAGES: 'Microservice {} does not have valid images', CATALOG_NOT_MATCH_IMAGES: 'Catalog item {} does not match provided images', @@ -71,9 +64,7 @@ module.exports = { INVALID_ROUTER_HOST: 'Invalid router host {}', CERT_PROPERTY_REQUIRED: 'Property "certificate" is required if property "requiresCert" is set to true', TUNNEL_NOT_FOUND: 'Tunnel not found', - STRACE_NOT_FOUND: 'Strace not found', INVALID_CONTENT_TYPE: 'Invalid content type', - UPLOADED_FILE_NOT_FOUND: 'Uploaded image snapshot file not found', APPLICATION_FILE_NOT_FOUND: 'Application file missing', REGISTRY_NOT_FOUND: 'Registry not found', USER_ALREADY_ACTIVATED: 'User is already activated.', diff --git a/src/routes/diagnostics.js b/src/routes/diagnostics.js deleted file mode 100644 index cf2d4dd8..00000000 --- a/src/routes/diagnostics.js +++ /dev/null @@ -1,218 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ -const constants = require('../helpers/constants') -const DiagnosticController = require('../controllers/diagnostic-controller') -const ResponseDecorator = require('../decorators/response-decorator') -const Errors = require('../helpers/errors') -const fs = require('fs') -const logger = require('../logger') -const rbacMiddleware = require('../lib/rbac/middleware') - -module.exports = [ - { - method: 'post', - path: '/api/v3/microservices/:uuid/image-snapshot', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_CREATED - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route - await rbacMiddleware.protect()(req, res, async () => { - const createMicroserviceImageSnapshotEndPoint = ResponseDecorator.handleErrors( - DiagnosticController.createMicroserviceImageSnapshotEndPoint, - successCode, - errorCodes - ) - const responseObject = await createMicroserviceImageSnapshotEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'get', - path: '/api/v3/microservices/:uuid/image-snapshot', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route - await rbacMiddleware.protect()(req, res, async () => { - const getMicroserviceImageSnapshotEndPoint = ResponseDecorator.handleErrors( - DiagnosticController.getMicroserviceImageSnapshotEndPoint, - successCode, - errorCodes - ) - const responseObject = await getMicroserviceImageSnapshotEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - if (responseObject.code !== successCode) { - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - } else { - res.writeHead(successCode, { - 'Content-Length': responseObject.body['Content-Length'], - 'Content-Type': responseObject.body['Content-Type'], - 'Content-Disposition': 'attachment; filename=' + responseObject.body.fileName - }) - fs.createReadStream(responseObject.body.filePath).pipe(res) - } - }) - } - }, - { - method: 'patch', - path: '/api/v3/microservices/:uuid/strace', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_NO_CONTENT - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route - await rbacMiddleware.protect()(req, res, async () => { - const changeMicroserviceStraceStateEndPoint = ResponseDecorator.handleErrors( - DiagnosticController.changeMicroserviceStraceStateEndPoint, - successCode, - errorCodes - ) - const responseObject = await changeMicroserviceStraceStateEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'get', - path: '/api/v3/microservices/:uuid/strace', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route - await rbacMiddleware.protect()(req, res, async () => { - const getMicroserviceStraceDataEndPoint = ResponseDecorator.handleErrors( - DiagnosticController.getMicroserviceStraceDataEndPoint, - successCode, - errorCodes - ) - const responseObject = await getMicroserviceStraceDataEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'put', - path: '/api/v3/microservices/:uuid/strace', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_NO_CONTENT - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - }, - { - code: constants.HTTP_CODE_INTERNAL_ERROR, - errors: [Errors.FtpError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route - await rbacMiddleware.protect()(req, res, async () => { - const postMicroserviceStraceDataToFtpEndPoint = ResponseDecorator.handleErrors( - DiagnosticController.postMicroserviceStraceDataToFtpEndPoint, - successCode, - errorCodes - ) - const responseObject = await postMicroserviceStraceDataToFtpEndPoint(req) - const user = req.kauth.grant.access_token.content.preferred_username - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - } -] diff --git a/src/schemas/agent.js b/src/schemas/agent.js index d5f5629d..2c32e24d 100644 --- a/src/schemas/agent.js +++ b/src/schemas/agent.js @@ -122,30 +122,6 @@ const updateAgentStatus = { 'additionalProperties': true } -const updateAgentStrace = { - 'id': '/updateAgentStrace', - 'type': 'object', - 'properties': { - 'straceData': { - 'type': 'array', - 'items': { '$ref': '/straceData' }, - 'required': [] - } - }, - 'additionalProperties': true -} - -const straceData = { - 'id': '/straceData', - 'type': 'object', - 'properties': { - 'microserviceUuid': { 'type': 'string' }, - 'buffer': { 'type': 'string' } - }, - 'required': ['microserviceUuid', 'buffer'], - 'additionalProperties': true -} - const microserviceStatus = { 'id': '/microserviceStatus', 'type': 'object', @@ -187,7 +163,7 @@ const updateUsbInfo = { } module.exports = { - mainSchemas: [agentProvision, agentDeprovision, updateAgentConfig, updateAgentGps, updateAgentStatus, updateAgentStrace, + mainSchemas: [agentProvision, agentDeprovision, updateAgentConfig, updateAgentGps, updateAgentStatus, updateHardwareInfo, updateUsbInfo], - innerSchemas: [straceData, microserviceStatus] + innerSchemas: [microserviceStatus] } diff --git a/src/schemas/diagnostics.js b/src/schemas/diagnostics.js deleted file mode 100644 index 8947c331..00000000 --- a/src/schemas/diagnostics.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const straceStateUpdate = { - 'id': '/straceStateUpdate', - 'type': 'object', - 'properties': { - 'enable': { 'type': 'boolean' } - }, - 'required': ['enable'] -} - -const straceGetData = { - 'id': '/straceGetData', - 'type': 'object', - 'properties': { - 'format': { 'enum': ['string', 'file'] } - }, - 'required': ['format'] -} - -const stracePostToFtp = { - 'id': '/stracePostToFtp', - 'type': 'object', - 'properties': { - 'ftpHost': { 'type': 'string' }, - 'ftpPort': { 'type': 'integer', 'minimum': 0 }, - 'ftpUser': { 'type': 'string' }, - 'ftpPass': { 'type': 'string' }, - 'ftpDestDir': { 'type': 'string' } - }, - 'required': ['ftpHost', 'ftpPort', 'ftpUser', 'ftpPass', 'ftpDestDir'] -} - -module.exports = { - mainSchemas: [straceStateUpdate, straceGetData, stracePostToFtp], - innerSchemas: [] -} diff --git a/src/schemas/microservice.js b/src/schemas/microservice.js index c5a17637..33fba63e 100644 --- a/src/schemas/microservice.js +++ b/src/schemas/microservice.js @@ -33,7 +33,6 @@ const microserviceCreate = { 'maximum': 100 }, 'logSize': { 'type': 'integer' }, - 'imageSnapshot': { 'type': 'string' }, 'volumeMappings': { 'type': 'array', 'items': { '$ref': '/volumeMappings' } }, diff --git a/src/services/diagnostic-service.js b/src/services/diagnostic-service.js deleted file mode 100644 index 41ace819..00000000 --- a/src/services/diagnostic-service.js +++ /dev/null @@ -1,243 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const TransactionDecorator = require('../decorators/transaction-decorator') -const AppHelper = require('../helpers/app-helper') -const Errors = require('../helpers/errors') -const ErrorMessages = require('../helpers/error-messages') -const Validator = require('../schemas/index') -const MicroserviceService = require('../services/microservices-service') -const StraceDiagnosticManager = require('../data/managers/strace-diagnostics-manager') -const ChangeTrackingService = require('./change-tracking-service') -const MicroserviceManager = require('../data/managers/microservice-manager') -const Config = require('../config') -const fs = require('fs') -const logger = require('../logger') -const FtpClient = require('ftp') -const mime = require('mime') - -const changeMicroserviceStraceState = async function (uuid, data, isCLI, transaction) { - await Validator.validate(data, Validator.schemas.straceStateUpdate) - const microservice = await MicroserviceService.getMicroserviceEndPoint(uuid, isCLI, transaction) - if (microservice.iofogUuid === null) { - throw new Errors.ValidationError(ErrorMessages.STRACE_WITHOUT_FOG) - } - - const straceObj = { - straceRun: data.enable, - microserviceUuid: uuid - } - - await StraceDiagnosticManager.updateOrCreate({ microserviceUuid: uuid }, straceObj, transaction) - await ChangeTrackingService.update(microservice.iofogUuid, ChangeTrackingService.events.diagnostics, transaction) -} - -const getMicroserviceStraceData = async function (uuid, data, isCLI, transaction) { - await Validator.validate(data, Validator.schemas.straceGetData) - - const microserviceWhere = isCLI - ? { uuid: uuid } - : { uuid: uuid } - const microservice = await MicroserviceManager.findOne(microserviceWhere, transaction) - if (!microservice) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, uuid)) - } - - const straceData = await StraceDiagnosticManager.findOne({ microserviceUuid: uuid }, transaction) - if (!straceData) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_STRACE, uuid)) - } - - const dir = Config.get('diagnostics.directory') || 'diagnostics' - const filePath = dir + '/' + uuid - - let result = straceData.buffer - - if (data.format === 'file') { - _createDirectoryIfNotExists(dir) - _writeBufferToFile(filePath, straceData.buffer) - result = _convertFileToBase64(filePath) - _deleteFile(filePath) - } - - return { - data: result - } -} - -const postMicroserviceStraceDatatoFtp = async function (uuid, data, isCLI, transaction) { - await Validator.validate(data, Validator.schemas.stracePostToFtp) - - const microserviceWhere = isCLI - ? { uuid: uuid } - : { uuid: uuid } - const microservice = await MicroserviceManager.findOne(microserviceWhere, transaction) - if (!microservice) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, uuid)) - } - const straceData = await StraceDiagnosticManager.findOne({ microserviceUuid: uuid }, transaction) - - if (!straceData) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_STRACE, uuid)) - } - - const dir = Config.get('diagnostics.directory') - const filePath = dir + '/' + uuid - - _createDirectoryIfNotExists(dir) - _writeBufferToFile(filePath, straceData.buffer) - await _sendFileToFtp(data, filePath) - _deleteFile(filePath) -} - -const postMicroserviceImageSnapshotCreate = async function (microserviceUuid, isCLI, transaction) { - const where = isCLI - ? { - uuid: microserviceUuid - } - : { - uuid: microserviceUuid - } - - const microservice = await MicroserviceManager.findOneWithDependencies(where, {}, transaction) - - if (!microservice) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) - } - if (microservice.iofogUuid === null) { - throw new Errors.ValidationError(ErrorMessages.IMAGE_SNAPSHOT_WITHOUT_FOG) - } - - const microserviceToUpdate = { - imageSnapshot: 'get_image' - } - - await MicroserviceManager.update({ uuid: microservice.uuid }, microserviceToUpdate, transaction) - await ChangeTrackingService.update(microservice.iofogUuid, ChangeTrackingService.events.imageSnapshot, transaction) -} - -const getMicroserviceImageSnapshot = async function (microserviceUuid, isCLI, transaction) { - const where = isCLI - ? { - uuid: microserviceUuid - } - : { - uuid: microserviceUuid - } - const microservice = await MicroserviceManager.findOneWithDependencies(where, {}, transaction) - if (!microservice) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) - } - if (microservice.iofogUuid === null) { - throw new Errors.ValidationError(ErrorMessages.IMAGE_SNAPSHOT_WITHOUT_FOG) - } - - const microserviceToUpdate = { - imageSnapshot: '' - } - - if (!microservice.imageSnapshot || microservice.imageSnapshot === 'get_image') { - throw new Errors.ValidationError(ErrorMessages.IMAGE_SNAPSHOT_NOT_AVAILABLE) - } - const _path = microservice.imageSnapshot - await MicroserviceManager.update({ uuid: microservice.uuid }, microserviceToUpdate, transaction) - if (isCLI) { - return _path - } else { - const mimetype = mime.lookup(microservice.imageSnapshot) - const stat = fs.statSync(_path) - const fileSize = stat.size - return { - 'Content-Length': fileSize, - 'Content-Type': mimetype, - 'fileName': _path.split(new RegExp('/'))[1], - 'filePath': _path - } - } -} - -const _sendFileToFtp = async function (data, filePath) { - const destDir = data.ftpDestDir - const connectionData = { - host: data.ftpHost, - port: data.ftpPort, - user: data.ftpUser, - password: data.ftpPass, - protocol: 'ftp' - } - - await writeFileToFtp(connectionData, filePath, destDir) -} - -const writeFileToFtp = function (connectionData, filePath, destDir) { - return new Promise((resolve, reject) => { - const client = new FtpClient() - - client.on('ready', () => { - client.put(filePath, destDir + '/' + filePath.split('/').pop(), (err) => { - if (err) { - client.end() - logger.warn(AppHelper.formatMessage(ErrorMessages.FTP_ERROR, err)) - reject(new Errors.FtpError(AppHelper.formatMessage(ErrorMessages.FTP_ERROR, err))) - } else { - client.end() - resolve() - } - }) - }) - - client.on('error', (err) => { - logger.warn(AppHelper.formatMessage(ErrorMessages.FTP_ERROR, err)) - reject(new Errors.FtpError(AppHelper.formatMessage(ErrorMessages.FTP_ERROR, err))) - }) - - client.connect(connectionData) - }) -} - -const _createDirectoryIfNotExists = function (dir) { - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir) - } -} - -const _writeBufferToFile = function (filePath, data) { - fs.writeFileSync(filePath, data, (err) => { - if (err) { - throw new Error(AppHelper.formatMessage(ErrorMessages.UNABLE_TO_WRITE_STRACE, data, err)) - } - }) -} - -const _convertFileToBase64 = function (filePath) { - const file = fs.readFileSync(filePath) - /* eslint-disable new-cap */ - return new Buffer.from(file).toString('base64') -} - -const _deleteFile = function (filePath) { - fs.unlink(filePath, (err) => { - if (err) { - logger.warn(AppHelper.formatMessage(ErrorMessages.UNABLE_TO_DELETE_STRACE, filePath, err)) - } - }) -} - -module.exports = { - changeMicroserviceStraceState: TransactionDecorator.generateTransaction(changeMicroserviceStraceState), - getMicroserviceStraceData: TransactionDecorator.generateTransaction(getMicroserviceStraceData), - postMicroserviceStraceDatatoFtp: TransactionDecorator.generateTransaction(postMicroserviceStraceDatatoFtp), - postMicroserviceImageSnapshotCreate: TransactionDecorator.generateTransaction(postMicroserviceImageSnapshotCreate), - getMicroserviceImageSnapshot: TransactionDecorator.generateTransaction(getMicroserviceImageSnapshot) - -} From ddae7bad4b7336fac943c162fd759aa3ff8707f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 04:17:49 +0300 Subject: [PATCH 15/75] Drop RBAC rules and OpenAPI paths for removed legacy APIs. Remove agent edgeResources/strace/snapshot endpoints, prune flows/edgeResources/diagnostics from RBAC and system roles, and sync swagger and event resource patterns. --- docs/swagger.json | 4954 +++++++++++------------ docs/swagger.yaml | 927 +---- src/config/rbac-resources.yaml | 82 - src/config/rbac-system-roles.js | 6 +- src/controllers/agent-controller.js | 27 - src/routes/agent.js | 145 - src/services/agent-service.js | 131 +- src/services/change-tracking-service.js | 11 - src/services/event-service.js | 3 - 9 files changed, 2479 insertions(+), 3807 deletions(-) diff --git a/docs/swagger.json b/docs/swagger.json index 16aac50d..17772b49 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1,3189 +1,3031 @@ { - "openapi" : "3.0.0", - "info" : { - "version" : "1.0.0", - "title" : "Datasance PoT Controller" + "openapi": "3.0.0", + "info": { + "version": "1.0.0", + "title": "Datasance PoT Controller" }, - "tags" : [ { - "name" : "Controller", - "description" : "Manage your controller" - }, { - "name" : "ioFog", - "description" : "Manage your agents" - }, { - "name" : "Application", - "description" : "Manage your applications" - }, { - "name" : "Application Template", - "description" : "Manage your application templates" - }, { - "name" : "Catalog", - "description" : "Manage your catalog" - }, { - "name" : "Registries", - "description" : "Manage your registries" - }, { - "name" : "Microservices", - "description" : "Manage your microservices" - }, { - "name" : "Routing", - "description" : "Manage your routes" - }, { - "name" : "Edge Resource", - "description" : "Manage your Edge Resources" - }, { - "name" : "Diagnostics", - "description" : "Diagnostic your microservices" - }, { - "name" : "Tunnel", - "description" : "Manage ssh tunnels" - }, { - "name" : "Agent", - "description" : "Used by your agents to communicate with your controller" - }, { - "name" : "User", - "description" : "Manage your users" - } ], - "paths" : { - "/status" : { - "get" : { - "tags" : [ "Controller" ], - "summary" : "Returns service health status", - "operationId" : "getServiceStatus", - "parameters" : [ ], - "responses" : { - "200" : { - "description" : "Service status" - }, - "500" : { - "description" : "Internal Server Error" + "tags": [ + { + "name": "Controller", + "description": "Manage your controller" + }, + { + "name": "ioFog", + "description": "Manage your agents" + }, + { + "name": "Application", + "description": "Manage your applications" + }, + { + "name": "Application Template", + "description": "Manage your application templates" + }, + { + "name": "Catalog", + "description": "Manage your catalog" + }, + { + "name": "Registries", + "description": "Manage your registries" + }, + { + "name": "Microservices", + "description": "Manage your microservices" + }, + { + "name": "Routing", + "description": "Manage your routes" + }, + { + "name": "Tunnel", + "description": "Manage ssh tunnels" + }, + { + "name": "Agent", + "description": "Used by your agents to communicate with your controller" + }, + { + "name": "User", + "description": "Manage your users" + } + ], + "paths": { + "/status": { + "get": { + "tags": [ + "Controller" + ], + "summary": "Returns service health status", + "operationId": "getServiceStatus", + "parameters": [], + "responses": { + "200": { + "description": "Service status" + }, + "500": { + "description": "Internal Server Error" } } } }, - "/fog-types" : { - "get" : { - "tags" : [ "Controller" ], - "summary" : "Gets ioFog types list", - "operationId" : "getIOFogTypes", - "parameters" : [ ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/fog-types": { + "get": { + "tags": [ + "Controller" + ], + "summary": "Gets ioFog types list", + "operationId": "getIOFogTypes", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog-list" : { - "get" : { - "tags" : [ "ioFog" ], - "summary" : "Returns list of ioFog nodes", - "operationId" : "getIOFogNodes", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "List of ioFog nodes", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog-list": { + "get": { + "tags": [ + "ioFog" + ], + "summary": "Returns list of ioFog nodes", + "operationId": "getIOFogNodes", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "List of ioFog nodes", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog" : { - "post" : { - "tags" : [ "ioFog" ], - "summary" : "Creates a new ioFog node", - "operationId" : "createIOFogNode", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog": { + "post": { + "tags": [ + "ioFog" + ], + "summary": "Creates a new ioFog node", + "operationId": "createIOFogNode", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}" : { - "get" : { - "tags" : [ "ioFog" ], - "summary" : "Gets ioFog node info", - "operationId" : "getIOFogNode", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}": { + "get": { + "tags": [ + "ioFog" + ], + "summary": "Gets ioFog node info", + "operationId": "getIOFogNode", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "delete" : { - "tags" : [ "ioFog" ], - "summary" : "Deletes an ioFog node", - "operationId" : "deleteIOFogNode", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "202" : { - "description" : "Accepted", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Node Id" - }, - "500" : { - "description" : "Internal Server Error" + "delete": { + "tags": [ + "ioFog" + ], + "summary": "Deletes an ioFog node", + "operationId": "deleteIOFogNode", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "202": { + "description": "Accepted", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "401": { + "description": "Not Authorized" + }, + "404": { + "description": "Invalid Node Id" + }, + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "ioFog" ], - "summary" : "Updates existing ioFog node", - "operationId" : "updateIOFogNode", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Updated", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "patch": { + "tags": [ + "ioFog" + ], + "summary": "Updates existing ioFog node", + "operationId": "updateIOFogNode", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Updated", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/provisioning-key" : { - "get" : { - "tags" : [ "ioFog" ], - "summary" : "Generates provisioning key for an ioFog node", - "operationId" : "generateProvisioningKey", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}/provisioning-key": { + "get": { + "tags": [ + "ioFog" + ], + "summary": "Generates provisioning key for an ioFog node", + "operationId": "generateProvisioningKey", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/version/{versionCommand}" : { - "post" : { - "tags" : [ "ioFog" ], - "summary" : "Set change version command", - "operationId" : "setVersionCommand", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - }, { - "name" : "versionCommand", - "in" : "path", - "description" : "change version command", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Node Id" - }, - "500" : { - "description" : "Internal Server Error" + "/iofog/{uuid}/version/{versionCommand}": { + "post": { + "tags": [ + "ioFog" + ], + "summary": "Set change version command", + "operationId": "setVersionCommand", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + }, + { + "name": "versionCommand", + "in": "path", + "description": "change version command", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "400": { + "description": "Bad Request" + }, + "401": { + "description": "Not Authorized" + }, + "404": { + "description": "Invalid Node Id" + }, + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/reboot" : { - "post" : { - "tags" : [ "ioFog" ], - "summary" : "remote reboot fog agent", - "operationId" : "setRebootCommand", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}/reboot": { + "post": { + "tags": [ + "ioFog" + ], + "summary": "remote reboot fog agent", + "operationId": "setRebootCommand", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/prune" : { - "post" : { - "tags" : [ "ioFog" ], - "summary" : "prune remote fog agent", - "operationId" : "setPruneCommand", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}/prune": { + "post": { + "tags": [ + "ioFog" + ], + "summary": "prune remote fog agent", + "operationId": "setPruneCommand", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/hal/hw" : { - "get" : { - "tags" : [ "ioFog" ], - "summary" : "Retrieves HAL hardware info", - "operationId" : "getFogHalHardwareInfo", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}/hal/hw": { + "get": { + "tags": [ + "ioFog" + ], + "summary": "Retrieves HAL hardware info", + "operationId": "getFogHalHardwareInfo", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/hal/usb" : { - "get" : { - "tags" : [ "ioFog" ], - "summary" : "Retrieves HAL USB info", - "operationId" : "getFogHalUsbInfo", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}/hal/usb": { + "get": { + "tags": [ + "ioFog" + ], + "summary": "Retrieves HAL USB info", + "operationId": "getFogHalUsbInfo", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/application" : { - "get" : { - "tags" : [ "Application" ], - "summary" : "Lists all applications", - "operationId" : "listApplication", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/application": { + "get": { + "tags": [ + "Application" + ], + "summary": "Lists all applications", + "operationId": "listApplication", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/application/system" : { - "get" : { - "tags" : [ "Application" ], - "summary" : "Lists all system applications", - "operationId" : "listSystemApplication", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/application/system": { + "get": { + "tags": [ + "Application" + ], + "summary": "Lists all system applications", + "operationId": "listSystemApplication", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/application/system/{name}" : { - "delete" : { - "tags" : [ "Application" ], - "summary" : "Deletes a system application", - "operationId" : "deleteSystemApplication", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/application/system/{name}": { + "delete": { + "tags": [ + "Application" + ], + "summary": "Deletes a system application", + "operationId": "deleteSystemApplication", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/application/yaml" : { - "post" : { - "tags" : [ "Application" ], - "summary" : "Creates an application using a YAML file", - "operationId" : "createApplicationYAML", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/application/yaml": { + "post": { + "tags": [ + "Application" + ], + "summary": "Creates an application using a YAML file", + "operationId": "createApplicationYAML", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/application/{name}" : { - "get" : { - "tags" : [ "Application" ], - "summary" : "Gets an application details", - "operationId" : "getApplication", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/application/{name}": { + "get": { + "tags": [ + "Application" + ], + "summary": "Gets an application details", + "operationId": "getApplication", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "delete" : { - "tags" : [ "Application" ], - "summary" : "Deletes an application", - "operationId" : "deleteApplication", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "delete": { + "tags": [ + "Application" + ], + "summary": "Deletes an application", + "operationId": "deleteApplication", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "Application" ], - "summary" : "Updates an application metadata", - "operationId" : "patchApplication", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "patch": { + "tags": [ + "Application" + ], + "summary": "Updates an application metadata", + "operationId": "patchApplication", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" } } } }, - "/application/yaml/{name}" : { - "put" : { - "tags" : [ "Application" ], - "summary" : "Updates an application using a YAML file", - "operationId" : "updateApplicationYAML", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/application/yaml/{name}": { + "put": { + "tags": [ + "Application" + ], + "summary": "Updates an application using a YAML file", + "operationId": "updateApplicationYAML", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/applicationTemplates" : { - "get" : { - "tags" : [ "Application Template" ], - "summary" : "Lists all application templates", - "operationId" : "listApplicationTemplates", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/applicationTemplates": { + "get": { + "tags": [ + "Application Template" + ], + "summary": "Lists all application templates", + "operationId": "listApplicationTemplates", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/applicationTemplate/yaml" : { - "post" : { - "tags" : [ "Application Template" ], - "summary" : "Creates an application template using a YAML file", - "operationId" : "createApplicationTemplateYAML", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/applicationTemplate/yaml": { + "post": { + "tags": [ + "Application Template" + ], + "summary": "Creates an application template using a YAML file", + "operationId": "createApplicationTemplateYAML", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/applicationTemplate/{name}" : { - "get" : { - "tags" : [ "Application Template" ], - "summary" : "Gets an application template", - "operationId" : "getApplicationTemplate", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application template name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/applicationTemplate/{name}": { + "get": { + "tags": [ + "Application Template" + ], + "summary": "Gets an application template", + "operationId": "getApplicationTemplate", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application template name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "delete" : { - "tags" : [ "Application Template" ], - "summary" : "Deletes an application template", - "operationId" : "deleteApplicationTemplate", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application template name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "delete": { + "tags": [ + "Application Template" + ], + "summary": "Deletes an application template", + "operationId": "deleteApplicationTemplate", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application template name", + "required": true } - } - }, - "patch" : { - "tags" : [ "Application Template" ], - "summary" : "Patches an application template", - "operationId" : "patchApplicationTemplate", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application template name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + ], + "security": [ + { + "userToken": [] } - } - } - }, - "/applicationTemplate/yaml/{name}" : { - "put" : { - "tags" : [ "Application Template" ], - "summary" : "Updates or creates an application template", - "operationId" : "updateOrCreateApplicationTemplateFromYaml", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application template name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "500" : { - "description" : "Internal Server Error" + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" } } - } - }, - "/agent/provision" : { - "post" : { - "tags" : [ "Agent" ], - "summary" : "Provision agent with an ioFog node", - "operationId" : "agentProvision", - "parameters" : [ ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + }, + "patch": { + "tags": [ + "Application Template" + ], + "summary": "Patches an application template", + "operationId": "patchApplicationTemplate", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application template name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Expired Provisioning Key" + "404": { + "description": "Not Found" }, - "404" : { - "description" : "Invalid Provisioning Key" - }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/deprovision" : { - "post" : { - "tags" : [ "Agent" ], - "summary" : "Deprovision agent", - "operationId" : "agentDeprovision", - "parameters" : [ ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/applicationTemplate/yaml/{name}": { + "put": { + "tags": [ + "Application Template" + ], + "summary": "Updates or creates an application template", + "operationId": "updateOrCreateApplicationTemplateFromYaml", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application template name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" - }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Bad Request" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/config" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Get an ioFog node configuration", - "operationId" : "getIOFogNodeConfig", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/provision": { + "post": { + "tags": [ + "Agent" + ], + "summary": "Provision agent with an ioFog node", + "operationId": "agentProvision", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Bad Request" + }, + "401": { + "description": "Expired Provisioning Key" + }, + "404": { + "description": "Invalid Provisioning Key" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } - }, - "patch" : { - "tags" : [ "Agent" ], - "summary" : "Updates an ioFog node configuration", - "operationId" : "updateIOFogNodeConfig", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + } + }, + "/agent/deprovision": { + "post": { + "tags": [ + "Agent" + ], + "summary": "Deprovision agent", + "operationId": "agentDeprovision", + "parameters": [], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/config/changes" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Gets ioFog node changes", - "operationId" : "getIOFogNodeChanges", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/config": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Get an ioFog node configuration", + "operationId": "getIOFogNodeConfig", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "401": { + "description": "Not Authorized" }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "Agent" ], - "summary" : "Resets ioFog node changes list", - "operationId" : "resetIOFogNodeChanges", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "patch": { + "tags": [ + "Agent" + ], + "summary": "Updates an ioFog node configuration", + "operationId": "updateIOFogNodeConfig", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/status" : { - "put" : { - "tags" : [ "Agent" ], - "summary" : "Posts agent status to ioFog node", - "operationId" : "postAgentStatus", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/config/changes": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Gets ioFog node changes", + "operationId": "getIOFogNodeChanges", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } - } - }, - "/agent/microservices" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Gets microservices running on an ioFog node", - "operationId" : "getAgentMicroservicesList", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + }, + "patch": { + "tags": [ + "Agent" + ], + "summary": "Resets ioFog node changes list", + "operationId": "resetIOFogNodeChanges", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Bad Request" + }, + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/microservices/{microserviceUuid}" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Gets microservices running on an ioFog node", - "operationId" : "getAgentMicroserviceInfo", - "parameters" : [ { - "name" : "microserviceUuid", - "in" : "path", - "description" : "Microservice UUID", - "required" : true - } ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/status": { + "put": { + "tags": [ + "Agent" + ], + "summary": "Posts agent status to ioFog node", + "operationId": "postAgentStatus", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Invalid Microservice Uuid" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/registries" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Gets list of Docker registries", - "operationId" : "getRegistriesList", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/microservices": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Gets microservices running on an ioFog node", + "operationId": "getAgentMicroservicesList", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/tunnel" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Get an ioFog node tunnel configuration", - "operationId" : "getIOFogNodeTunnelConfig", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/microservices/{microserviceUuid}": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Gets microservices running on an ioFog node", + "operationId": "getAgentMicroserviceInfo", + "parameters": [ + { + "name": "microserviceUuid", + "in": "path", + "description": "Microservice UUID", + "required": true + } + ], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Tunnel Not Found" + "404": { + "description": "Invalid Microservice Uuid" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/strace" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Get an ioFog node strace info", - "operationId" : "getIOFogNodeStraceInfo", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/registries": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Gets list of Docker registries", + "operationId": "getRegistriesList", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Strace Not Found" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } - }, - "put" : { - "tags" : [ "Agent" ], - "summary" : "Posts agent strace to ioFog node", - "operationId" : "postIOFogNodeStraceBuffer", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + } + }, + "/agent/tunnel": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Get an ioFog node tunnel configuration", + "operationId": "getIOFogNodeTunnelConfig", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" - }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Tunnel Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/version" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Get change version command", - "operationId" : "getChangeVersion", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/version": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Get change version command", + "operationId": "getChangeVersion", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Version Command Not Found" + "404": { + "description": "Version Command Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/hal/hw" : { - "put" : { - "tags" : [ "Agent" ], - "summary" : "Updates HAL hardware info", - "operationId" : "putHalHardwareInfo", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/hal/hw": { + "put": { + "tags": [ + "Agent" + ], + "summary": "Updates HAL hardware info", + "operationId": "putHalHardwareInfo", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/hal/usb" : { - "get" : { - "tags" : [ "ioFog" ], - "summary" : "Retrieves HAL USB info", - "operationId" : "getAgentHalUsbInfo", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/hal/usb": { + "get": { + "tags": [ + "ioFog" + ], + "summary": "Retrieves HAL USB info", + "operationId": "getAgentHalUsbInfo", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "put" : { - "tags" : [ "Agent" ], - "summary" : "Updates HAL USB info", - "operationId" : "putHalUsbInfo", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "put": { + "tags": [ + "Agent" + ], + "summary": "Updates HAL USB info", + "operationId": "putHalUsbInfo", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/delete-node" : { - "delete" : { - "tags" : [ "Agent" ], - "summary" : "Deletes an ioFog node", - "operationId" : "deleteAgentNode", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "No Content", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/delete-node": { + "delete": { + "tags": [ + "Agent" + ], + "summary": "Deletes an ioFog node", + "operationId": "deleteAgentNode", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "204": { + "description": "No Content", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/image-snapshot" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Get image snapshot info", - "operationId" : "getImageSnapshot", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Image Snapshot Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "/agent/tracking": { + "post": { + "tags": [ + "Agent" + ], + "summary": "Post tracking info", + "operationId": "postTracking", + "parameters": [], + "security": [ + { + "agentToken": [] } - } - }, - "put" : { - "tags" : [ "Agent" ], - "summary" : "Put image snapshot info on controller", - "operationId" : "putImageSnapshot", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } + ], + "responses": { + "204": { + "description": "Success" }, - "400" : { - "description" : "Bad Request" + "401": { + "description": "Not Authorized" }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/tracking" : { - "post" : { - "tags" : [ "Agent" ], - "summary" : "Post tracking info", - "operationId" : "postTracking", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success" - }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + "/catalog/microservices": { + "get": { + "tags": [ + "Catalog" + ], + "summary": "Gets microservices catalog", + "operationId": "getMicroservicesCatalog", + "parameters": [], + "security": [ + { + "userToken": [] } - } - } - }, - "/catalog/microservices" : { - "get" : { - "tags" : [ "Catalog" ], - "summary" : "Gets microservices catalog", - "operationId" : "getMicroservicesCatalog", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "post" : { - "tags" : [ "Catalog" ], - "summary" : "Creates a new microservice catalog item", - "operationId" : "createMicroserviceCatalogItem", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "post": { + "tags": [ + "Catalog" + ], + "summary": "Creates a new microservice catalog item", + "operationId": "createMicroserviceCatalogItem", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "409" : { - "description" : "Duplicate Name" + "409": { + "description": "Duplicate Name" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/catalog/microservices/{id}" : { - "get" : { - "tags" : [ "Catalog" ], - "summary" : "Gets microservice catalog item info", - "operationId" : "getMicroserviceCatalogItem", - "parameters" : [ { - "name" : "id", - "in" : "path", - "description" : "Catalog Item Id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Catalog Item Info", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/catalog/microservices/{id}": { + "get": { + "tags": [ + "Catalog" + ], + "summary": "Gets microservice catalog item info", + "operationId": "getMicroserviceCatalogItem", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Catalog Item Id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Catalog Item Info", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Catalog Item Id" + "404": { + "description": "Invalid Catalog Item Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "delete" : { - "tags" : [ "Catalog" ], - "summary" : "Deletes a microservice catalog item", - "operationId" : "deleteMicroserviceCatalogItem", - "parameters" : [ { - "name" : "id", - "in" : "path", - "description" : "Catalog Item Id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Catalog Item Id" - }, - "500" : { - "description" : "Internal Server Error" + "delete": { + "tags": [ + "Catalog" + ], + "summary": "Deletes a microservice catalog item", + "operationId": "deleteMicroserviceCatalogItem", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Catalog Item Id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "401": { + "description": "Not Authorized" + }, + "404": { + "description": "Invalid Catalog Item Id" + }, + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "Catalog" ], - "summary" : "Updates a microservice catalog item", - "operationId" : "updateMicroserviceCatalogItem", - "parameters" : [ { - "name" : "id", - "in" : "path", - "description" : "Catalog Item Id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "patch": { + "tags": [ + "Catalog" + ], + "summary": "Updates a microservice catalog item", + "operationId": "updateMicroserviceCatalogItem", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Catalog Item Id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Catalog Item Id" + "404": { + "description": "Invalid Catalog Item Id" }, - "409" : { - "description" : "Duplicate Name" + "409": { + "description": "Duplicate Name" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices" : { - "get" : { - "tags" : [ "Microservices" ], - "summary" : "Gets list of microservices", - "operationId" : "getMicroservicesList", - "parameters" : [ { - "name" : "flowId", - "in" : "query", - "description" : "Flow Id", - "required" : false - }, { - "name" : "application", - "in" : "query", - "description" : "Application name", - "required" : false - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + "/microservices": { + "get": { + "tags": [ + "Microservices" + ], + "summary": "Gets list of microservices", + "operationId": "getMicroservicesList", + "parameters": [ + { + "name": "flowId", + "in": "query", + "description": "Flow Id", + "required": false + }, + { + "name": "application", + "in": "query", + "description": "Application name", + "required": false + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "401": { + "description": "Not Authorized" + }, + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/yaml" : { - "post" : { - "tags" : [ "Microservices" ], - "summary" : "Creates a new microservice in an Application", - "operationId" : "createMicroserviceYAML", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/microservices/yaml": { + "post": { + "tags": [ + "Microservices" + ], + "summary": "Creates a new microservice in an Application", + "operationId": "createMicroserviceYAML", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "409" : { - "description" : "Duplicate Name" + "409": { + "description": "Duplicate Name" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/{uuid}" : { - "get" : { - "tags" : [ "Microservices" ], - "summary" : "Gets a microservice info", - "operationId" : "getMicroserviceInfo", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/microservices/{uuid}": { + "get": { + "tags": [ + "Microservices" + ], + "summary": "Gets a microservice info", + "operationId": "getMicroserviceInfo", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "delete" : { - "tags" : [ "Microservices" ], - "summary" : "Deletes a microservice", - "operationId" : "deleteMicroservice", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "delete": { + "tags": [ + "Microservices" + ], + "summary": "Deletes a microservice", + "operationId": "deleteMicroservice", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "Microservices" ], - "summary" : "Updates a microservice", - "operationId" : "updateMicroservice", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ] - } - }, - "/microservices/system/{uuid}" : { - "patch" : { - "tags" : [ "Microservices" ], - "summary" : "Updates a system microservice", - "operationId" : "updateSystemMicroservice", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ] - } - }, - "/microservices/yaml/{uuid}" : { - "patch" : { - "tags" : [ "Microservices" ], - "summary" : "Updates a microservice", - "operationId" : "updateMicroserviceYAML", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "patch": { + "tags": [ + "Microservices" + ], + "summary": "Updates a microservice", + "operationId": "updateMicroservice", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ] + } + }, + "/microservices/system/{uuid}": { + "patch": { + "tags": [ + "Microservices" + ], + "summary": "Updates a system microservice", + "operationId": "updateSystemMicroservice", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ] + } + }, + "/microservices/yaml/{uuid}": { + "patch": { + "tags": [ + "Microservices" + ], + "summary": "Updates a microservice", + "operationId": "updateMicroserviceYAML", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "400": { + "description": "Bad Request" + }, + "401": { + "description": "Not Authorized" + }, + "404": { + "description": "Not Found" + }, + "409": { + "description": "Duplicate Name" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/microservices/{uuid}/port-mapping": { + "get": { + "tags": [ + "Microservices" + ], + "summary": "Get a port mapping list for microservice", + "operationId": "getMicroservicePortMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "401": { + "description": "Not Authorized" + }, + "404": { + "description": "Not Found" }, - "401" : { - "description" : "Not Authorized" + "500": { + "description": "Internal Server Error" + } + } + }, + "post": { + "tags": [ + "Microservices" + ], + "summary": "Creates a port mapping for microservice", + "operationId": "createMicroservicePortMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "400": { + "description": "Not Valid" }, - "404" : { - "description" : "Not Found" + "401": { + "description": "Not Authorized" }, - "409" : { - "description" : "Duplicate Name" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/{uuid}/port-mapping" : { - "get" : { - "tags" : [ "Microservices" ], - "summary" : "Get a port mapping list for microservice", - "operationId" : "getMicroservicePortMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/microservices/system/{uuid}/port-mapping": { + "post": { + "tags": [ + "Microservices" + ], + "summary": "Creates a port mapping for system microservice", + "operationId": "createSystemMicroservicePortMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Not Valid" }, - "404" : { - "description" : "Not Found" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" } } - }, - "post" : { - "tags" : [ "Microservices" ], - "summary" : "Creates a port mapping for microservice", - "operationId" : "createMicroservicePortMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + } + }, + "/microservices/{uuid}/port-mapping/{internalPort}": { + "delete": { + "tags": [ + "Microservices" + ], + "summary": "Deletes a port mapping for microservice", + "operationId": "deleteMicroservicePortMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + }, + { + "name": "internalPort", + "in": "path", + "description": "Internal Port", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Not Valid" + "401": { + "description": "Not Authorized" }, - "401" : { - "description" : "Not Authorized" + "404": { + "description": "Not Found" }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/system/{uuid}/port-mapping" : { - "post" : { - "tags" : [ "Microservices" ], - "summary" : "Creates a port mapping for system microservice", - "operationId" : "createSystemMicroservicePortMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/microservices/system/{uuid}/port-mapping/{internalPort}": { + "delete": { + "tags": [ + "Microservices" + ], + "summary": "Deletes a port mapping for system microservice", + "operationId": "deleteSystemMicroservicePortMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + }, + { + "name": "internalPort", + "in": "path", + "description": "Internal Port", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Not Valid" + "401": { + "description": "Not Authorized" }, - "401" : { - "description" : "Not Authorized" + "404": { + "description": "Not Found" }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/{uuid}/port-mapping/{internalPort}" : { - "delete" : { - "tags" : [ "Microservices" ], - "summary" : "Deletes a port mapping for microservice", - "operationId" : "deleteMicroservicePortMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - }, { - "name" : "internalPort", - "in" : "path", - "description" : "Internal Port", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "/microservices/{uuid}/volume-mapping": { + "get": { + "tags": [ + "Microservices" + ], + "summary": "Get a volume mapping list for microservice", + "operationId": "getMicroserviceVolumeMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true } - } - } - }, - "/microservices/system/{uuid}/port-mapping/{internalPort}" : { - "delete" : { - "tags" : [ "Microservices" ], - "summary" : "Deletes a port mapping for system microservice", - "operationId" : "deleteSystemMicroservicePortMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - }, { - "name" : "internalPort", - "in" : "path", - "description" : "Internal Port", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + ], + "security": [ + { + "userToken": [] } - } - } - }, - "/microservices/{uuid}/volume-mapping" : { - "get" : { - "tags" : [ "Microservices" ], - "summary" : "Get a volume mapping list for microservice", - "operationId" : "getMicroserviceVolumeMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "post" : { - "tags" : [ "Microservices" ], - "summary" : "Creates a volume mapping for microservice", - "operationId" : "createMicroserviceVolumeMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "post": { + "tags": [ + "Microservices" + ], + "summary": "Creates a volume mapping for microservice", + "operationId": "createMicroserviceVolumeMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Not Valid" + "400": { + "description": "Not Valid" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/system/{uuid}/volume-mapping" : { - "post" : { - "tags" : [ "Microservices" ], - "summary" : "Creates a volume mapping for system microservice", - "operationId" : "createSystemMicroserviceVolumeMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } + "/microservices/system/{uuid}/volume-mapping": { + "post": { + "tags": [ + "Microservices" + ], + "summary": "Creates a volume mapping for system microservice", + "operationId": "createSystemMicroserviceVolumeMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } } }, - "400" : { - "description" : "Not Valid" + "400": { + "description": "Not Valid" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/{uuid}/volume-mapping/{id}" : { - "delete" : { - "tags" : [ "Microservices" ], - "summary" : "Deletes a volume mapping for microservice", - "operationId" : "deleteMicroserviceVolumeMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - }, { - "name" : "id", - "in" : "path", - "description" : "Volume id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Not Valid" - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "/microservices/{uuid}/volume-mapping/{id}": { + "delete": { + "tags": [ + "Microservices" + ], + "summary": "Deletes a volume mapping for microservice", + "operationId": "deleteMicroserviceVolumeMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + }, + { + "name": "id", + "in": "path", + "description": "Volume id", + "required": true } - } - } - }, - "/microservices/system/{uuid}/volume-mapping/{id}" : { - "delete" : { - "tags" : [ "Microservices" ], - "summary" : "Deletes a volume mapping for system microservice", - "operationId" : "deleteSystemMicroserviceVolumeMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - }, { - "name" : "id", - "in" : "path", - "description" : "Volume id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Not Valid" - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + ], + "security": [ + { + "userToken": [] } - } - } - }, - "/microservices/{uuid}/image-snapshot" : { - "get" : { - "tags" : [ "Diagnostics" ], - "summary" : "Download image snapshot", - "operationId" : "downloadImageSnapshot", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice UUID", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Not Valid" }, - "404" : { - "description" : "Invalid Microservice UUID" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" - } - } - }, - "post" : { - "tags" : [ "Diagnostics" ], - "summary" : "Send request to create image snapshot", - "operationId" : "createImageSnapshot", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice UUID", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Microservice UUID" - }, - "500" : { - "description" : "Internal Server Error" + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/{uuid}/strace" : { - "get" : { - "tags" : [ "Diagnostics" ], - "summary" : "Gets Strace Data for Microservice", - "operationId" : "getMicroserviceStrace", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice UUID", - "required" : true - }, { - "name" : "format", - "in" : "query", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Microservice UUID" - }, - "500" : { - "description" : "Internal Server Error" - } - } - }, - "put" : { - "tags" : [ "Diagnostics" ], - "summary" : "Posts Microservice Strace file to FTP", - "operationId" : "postMicroserviceStraceToFTP", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice UUID", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" + "/microservices/system/{uuid}/volume-mapping/{id}": { + "delete": { + "tags": [ + "Microservices" + ], + "summary": "Deletes a volume mapping for system microservice", + "operationId": "deleteSystemMicroserviceVolumeMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Microservice UUID" - }, - "500" : { - "description" : "Internal Server Error" + { + "name": "id", + "in": "path", + "description": "Volume id", + "required": true } - } - }, - "patch" : { - "tags" : [ "Diagnostics" ], - "summary" : "Enables Microservice Strace Option", - "operationId" : "enableMicroserviceStrace", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice UUID", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Not Valid" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Microservice UUID" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/tunnel" : { - "get" : { - "tags" : [ "Tunnel" ], - "summary" : "Gets current info about ioFog node ssh tunnel status", - "operationId" : "getIOFogNodeSshTunnelStatusInfo", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}/tunnel": { + "get": { + "tags": [ + "Tunnel" + ], + "summary": "Gets current info about ioFog node ssh tunnel status", + "operationId": "getIOFogNodeSshTunnelStatusInfo", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "Tunnel" ], - "summary" : "Opens/closes ssh tunnel", - "operationId" : "openIOFogNodeSshTunnel", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "patch": { + "tags": [ + "Tunnel" + ], + "summary": "Opens/closes ssh tunnel", + "operationId": "openIOFogNodeSshTunnel", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/registries" : { - "get" : { - "tags" : [ "Registries" ], - "summary" : "Gets list of registries", - "operationId" : "getRegistryList", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/registries": { + "get": { + "tags": [ + "Registries" + ], + "summary": "Gets list of registries", + "operationId": "getRegistryList", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "post" : { - "tags" : [ "Registries" ], - "summary" : "Creates new registry", - "operationId" : "createRegistry", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "post": { + "tags": [ + "Registries" + ], + "summary": "Creates new registry", + "operationId": "createRegistry", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/registries/{id}" : { - "delete" : { - "tags" : [ "Registries" ], - "summary" : "Deletes a registry", - "operationId" : "deleteRegistry", - "parameters" : [ { - "name" : "id", - "in" : "path", - "description" : "Registry id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Deleted", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/registries/{id}": { + "delete": { + "tags": [ + "Registries" + ], + "summary": "Deletes a registry", + "operationId": "deleteRegistry", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Registry id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Deleted", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Registry Id" + "404": { + "description": "Invalid Registry Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "Registries" ], - "summary" : "Updates a registry", - "operationId" : "updateRegistry", - "parameters" : [ { - "name" : "id", - "in" : "path", - "description" : "Registry id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Updated", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "patch": { + "tags": [ + "Registries" + ], + "summary": "Updates a registry", + "operationId": "updateRegistry", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Registry id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Updated", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Registry Id" + "404": { + "description": "Invalid Registry Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/user/login" : { - "post" : { - "tags" : [ "User" ], - "summary" : "Login", - "operationId" : "login", - "parameters" : [ ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/user/login": { + "post": { + "tags": [ + "User" + ], + "summary": "Login", + "operationId": "login", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "bad request" + "400": { + "description": "bad request" }, - "401" : { - "description" : "incorrect credentials" + "401": { + "description": "incorrect credentials" } } } }, - "/user/refresh" : { - "post" : { - "tags" : [ "User" ], - "summary" : "Refresh accessToken with refreshToken", - "operationId" : "refresh", - "parameters" : [ ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/user/refresh": { + "post": { + "tags": [ + "User" + ], + "summary": "Refresh accessToken with refreshToken", + "operationId": "refresh", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "bad request" + "400": { + "description": "bad request" }, - "401" : { - "description" : "incorrect credentials" + "401": { + "description": "incorrect credentials" } } } }, - "/user/logout" : { - "post" : { - "tags" : [ "User" ], - "summary" : "Logout", - "operationId" : "logout", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success" + "/user/logout": { + "post": { + "tags": [ + "User" + ], + "summary": "Logout", + "operationId": "logout", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/user/profile" : { - "get" : { - "tags" : [ "User" ], - "summary" : "Get current user profile data", - "operationId" : "getUserProfile", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/user/profile": { + "get": { + "tags": [ + "User" + ], + "summary": "Get current user profile data", + "operationId": "getUserProfile", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/routes" : { - "get" : { - "tags" : [ "Routing" ], - "summary" : "Get routes", - "operationId" : "getRoutes", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/routes": { + "get": { + "tags": [ + "Routing" + ], + "summary": "Get routes", + "operationId": "getRoutes", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "post" : { - "tags" : [ "Routing" ], - "summary" : "Creates a new route", - "operationId" : "createRoute", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "post": { + "tags": [ + "Routing" + ], + "summary": "Creates a new route", + "operationId": "createRoute", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "409" : { - "description" : "Duplicate Name" + "409": { + "description": "Duplicate Name" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/routes/{name}" : { - "get" : { - "tags" : [ "Routing" ], - "summary" : "Gets a route info", - "operationId" : "getRoute", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Route name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Route Info", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/routes/{name}": { + "get": { + "tags": [ + "Routing" + ], + "summary": "Gets a route info", + "operationId": "getRoute", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Route name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Route Info", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Route Id" + "404": { + "description": "Invalid Route Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "delete" : { - "tags" : [ "Routing" ], - "summary" : "Deletes a route", - "operationId" : "deleteRoute", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Route name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Route Id" - }, - "500" : { - "description" : "Internal Server Error" + "delete": { + "tags": [ + "Routing" + ], + "summary": "Deletes a route", + "operationId": "deleteRoute", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Route name", + "required": true } - } - }, - "patch" : { - "tags" : [ "Routing" ], - "summary" : "Updates a route", - "operationId" : "updateRoute", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Route name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Route Id" - }, - "409" : { - "description" : "Duplicate Name" - }, - "500" : { - "description" : "Internal Server Error" + ], + "security": [ + { + "userToken": [] } - } - } - }, - "/edgeResources" : { - "get" : { - "tags" : [ "Edge Resource" ], - "summary" : "Get Edge Resources", - "operationId" : "getEdgeResources", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" - } - } - } - }, - "/edgeResource/{name}/{version}" : { - "get" : { - "tags" : [ "Edge Resource" ], - "summary" : "Get Specific Edge Resource", - "operationId" : "getEdgeResourceDetail", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Edge Resource name", - "required" : true - }, { - "name" : "version", - "in" : "path", - "description" : "Edge Resource version", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + "404": { + "description": "Invalid Route Id" + }, + "500": { + "description": "Internal Server Error" } } }, - "put" : { - "tags" : [ "Edge Resource" ], - "summary" : "Update/Create Specific Edge Resource", - "operationId" : "putEdgeResource", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Edge Resource name", - "required" : true - }, { - "name" : "version", - "in" : "path", - "description" : "Edge Resource version", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + "patch": { + "tags": [ + "Routing" + ], + "summary": "Updates a route", + "operationId": "updateRoute", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Route name", + "required": true } - } - }, - "delete" : { - "tags" : [ "Edge Resource" ], - "summary" : "Deletes an Edge Resource", - "operationId" : "deleteEdgeResource", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Edge Resource name", - "required" : true - }, { - "name" : "version", - "in" : "path", - "description" : "Edge Resource version", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "404" : { - "description" : "Not Found" - }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + ], + "security": [ + { + "userToken": [] } - } - } - }, - "/edgeResource/{name}" : { - "get" : { - "tags" : [ "Edge Resource" ], - "summary" : "Get Specific Edge Resource versions", - "operationId" : "getEdgeResourceVersions", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Edge Resource name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Bad Request" }, - "500" : { - "description" : "Internal Server Error" - } - } - } - }, - "/edgeResource" : { - "post" : { - "tags" : [ "Edge Resource" ], - "summary" : "Create Specific Edge Resource", - "operationId" : "postEdgeResource", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } + "401": { + "description": "Not Authorized" }, - "401" : { - "description" : "Not Authorized" + "404": { + "description": "Invalid Route Id" }, - "500" : { - "description" : "Internal Server Error" - } - } - } - }, - "/edgeResource/{name}/{version}/link" : { - "post" : { - "tags" : [ "Edge Resource" ], - "summary" : "Attach Edge Resource to Agent", - "operationId" : "postEdgeResourceLink", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Edge Resource Name", - "required" : true - }, { - "name" : "version", - "in" : "path", - "description" : "Edge Resource Version", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" - } - } - }, - "delete" : { - "tags" : [ "Edge Resource" ], - "summary" : "Detach Edge Resource from Agent", - "operationId" : "deleteEdgeResourceLink", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Edge Resource Name", - "required" : true - }, { - "name" : "version", - "in" : "path", - "description" : "Edge Resource Version", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "409": { + "description": "Duplicate Name" + }, + "500": { + "description": "Internal Server Error" } } } } }, - "definitions" : { } -} \ No newline at end of file + "definitions": {} +} diff --git a/docs/swagger.yaml b/docs/swagger.yaml index db43dcfa..fa6e72fd 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1335,61 +1335,6 @@ paths: description: Tunnel Not Found "500": description: Internal Server Error - /agent/strace: - get: - tags: - - Agent - summary: Get an ioFog node strace info - operationId: getIOFogNodeStraceInfo - security: - - authToken: [] - responses: - "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - content: - application/json: - schema: - $ref: "#/components/schemas/IOFogNodeStraceResponse" - "401": - description: Not Authorized - "404": - description: Strace Not Found - "500": - description: Internal Server Error - put: - tags: - - Agent - summary: Posts agent strace to ioFog node - operationId: postIOFogNodeStraceBuffer - security: - - authToken: [] - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/IOFogNodeStraceBuffer" - required: true - responses: - "204": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - "400": - description: Bad Request - "401": - description: Not Authorized - "404": - description: Invalid Node Id - "500": - description: Internal Server Error /agent/version: get: tags: @@ -1509,59 +1454,6 @@ paths: description: Not Authorized "500": description: Internal Server Error - /agent/image-snapshot: - get: - tags: - - Agent - summary: Get image snapshot info - operationId: getImageSnapshot - security: - - authToken: [] - responses: - "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - content: - application/json: - schema: - $ref: "#/components/schemas/ImageSnapshotResponse" - "401": - description: Not Authorized - "404": - description: Image Snapshot Not Found - "500": - description: Internal Server Error - put: - tags: - - Agent - summary: Put image snapshot info on controller - operationId: putImageSnapshot - security: - - authToken: [] - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ImageSnapshotRequest" - required: true - responses: - "204": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - "400": - description: Bad Request - "401": - description: Not Authorized - "500": - description: Internal Server Error /agent/tracking: post: tags: @@ -2889,195 +2781,6 @@ paths: description: Invalid Microservice UUID "500": description: Internal Server Error - "/microservices/{uuid}/image-snapshot": - post: - tags: - - Diagnostics - summary: Send request to create image snapshot - operationId: createImageSnapshot - parameters: - - in: path - name: uuid - description: Microservice UUID - required: true - schema: - type: string - security: - - authToken: [] - responses: - "201": - description: Created - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - content: - application/json: - schema: - type: object - properties: - id: - type: string - "401": - description: Not Authorized - "404": - description: Invalid Microservice UUID - "500": - description: Internal Server Error - get: - tags: - - Diagnostics - summary: Download image snapshot - operationId: downloadImageSnapshot - parameters: - - in: path - name: uuid - description: Microservice UUID - required: true - schema: - type: string - security: - - authToken: [] - responses: - "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - content: - application/gzip: - schema: - type: string - format: binary - "401": - description: Not Authorized - "404": - description: Invalid Microservice UUID - "500": - description: Internal Server Error - "/microservices/{uuid}/strace": - patch: - tags: - - Diagnostics - summary: Enables Microservice Strace Option - operationId: enableMicroserviceStrace - parameters: - - in: path - name: uuid - description: Microservice UUID - required: true - schema: - type: string - security: - - authToken: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - enable: - type: boolean - description: Strace info to enable or disable feature - required: true - responses: - "204": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - "400": - description: Bad Request - "401": - description: Not Authorized - "404": - description: Invalid Microservice UUID - "500": - description: Internal Server Error - get: - tags: - - Diagnostics - summary: Gets Strace Data for Microservice - operationId: getMicroserviceStrace - parameters: - - in: path - name: uuid - description: Microservice UUID - required: true - schema: - type: string - - in: query - name: format - required: true - schema: - type: string - enum: - - file - - string - security: - - authToken: [] - responses: - "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - content: - application/json: - schema: - type: object - properties: - data: - type: string - "401": - description: Not Authorized - "404": - description: Invalid Microservice UUID - "500": - description: Internal Server Error - put: - tags: - - Diagnostics - summary: Posts Microservice Strace file to FTP - operationId: postMicroserviceStraceToFTP - parameters: - - in: path - required: true - name: uuid - description: Microservice UUID - schema: - type: string - security: - - authToken: [] - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/MicroserviceStraceFTPBody" - required: true - responses: - "204": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - "400": - description: Bad Request - "401": - description: Not Authorized - "404": - description: Invalid Microservice UUID - "500": - description: Internal Server Error "/iofog/{uuid}/tunnel": patch: tags: @@ -3208,335 +2911,69 @@ paths: name: id description: Registry id required: true - schema: - type: string - security: - - authToken: [] - responses: - "204": - description: Deleted - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - "401": - description: Not Authorized - "404": - description: Invalid Registry Id - "500": - description: Internal Server Error - patch: - tags: - - Registries - summary: Updates a registry - operationId: updateRegistry - parameters: - - in: path - name: id - description: Registry id - required: true - schema: - type: string - security: - - authToken: [] - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/RegistryBody" - required: true - responses: - "204": - description: Updated - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - "400": - description: Bad Request - "401": - description: Not Authorized - "404": - description: Invalid Registry Id - "500": - description: Internal Server Error - get: - tags: - - Registries - summary: Gets a registry - operationId: getRegistry - parameters: - - in: path - name: id - description: Registry id - required: true - schema: - type: string - security: - - authToken: [] - responses: - "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - content: - application/json: - schema: - $ref: "#/components/schemas/RegistryResponse" - "401": - description: Not Authorized - "404": - description: Invalid Registry Id - "500": - description: Internal Server Error - /user/login: - post: - tags: - - User - summary: Login - operationId: login - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/LoginRequest" - required: true - responses: - "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - content: - application/json: - schema: - $ref: "#/components/schemas/LoginSuccessResponse" - "400": - description: bad request - "401": - description: incorrect credentials - /user/refresh: - post: - tags: - - User - summary: Refresh accessToken with refreshToken - operationId: refresh - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/RefreshRequest" - required: true - responses: - "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - content: - application/json: - schema: - $ref: "#/components/schemas/RefreshSuccessResponse" - "400": - description: bad request - "401": - description: incorrect credentials - /user/logout: - post: - tags: - - User - summary: Logout - operationId: logout - security: - - authToken: [] - responses: - "204": - description: Success - "401": - description: Not Authorized - "500": - description: Internal Server Error - /user/profile: - get: - tags: - - User - summary: Get current user profile data - operationId: getUserProfile - security: - - authToken: [] - responses: - "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - content: - application/json: - schema: - $ref: "#/components/schemas/UserProfileDetailsResponse" - "401": - description: Not Authorized - "500": - description: Internal Server Error - /edgeResources: - get: - tags: - - Edge Resource - summary: Get Edge Resources - operationId: getEdgeResources - security: - - authToken: [] - responses: - "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - content: - application/json: - schema: - $ref: "#/components/schemas/EdgeResourcesListResponse" - "401": - description: Not Authorized - "500": - description: Internal Server Error - "/edgeResource/{name}/{version}": - get: - tags: - - Edge Resource - summary: Get Specific Edge Resource - operationId: getEdgeResourceDetail - parameters: - - in: path - name: name - description: Edge Resource name - required: true - schema: - type: string - - in: path - name: version - description: Edge Resource version - required: true - schema: - type: string - security: - - authToken: [] - responses: - "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - content: - application/json: - schema: - $ref: "#/components/schemas/EdgeResourceGetResponse" - "401": - description: Not Authorized - "500": - description: Internal Server Error - put: - tags: - - Edge Resource - summary: Update/Create Specific Edge Resource - operationId: putEdgeResource - parameters: - - in: path - name: name - description: Edge Resource name - required: true - schema: - type: string - - in: path - name: version - description: Edge Resource version - required: true - schema: - type: string - security: - - authToken: [] - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/EdgeResourceCreateSchema" - description: Updated profile data - required: true + schema: + type: string + security: + - authToken: [] responses: - "200": - description: Success + "204": + description: Deleted headers: X-Timestamp: description: FogController server timestamp schema: type: number - content: - application/json: - schema: - $ref: "#/components/schemas/EdgeResourceCreateResponse" "401": description: Not Authorized + "404": + description: Invalid Registry Id "500": description: Internal Server Error - delete: + patch: tags: - - Edge Resource - summary: Deletes an Edge Resource - operationId: deleteEdgeResource + - Registries + summary: Updates a registry + operationId: updateRegistry parameters: - in: path - name: name - description: Edge Resource name - required: true - schema: - type: string - - in: path - name: version - description: Edge Resource version + name: id + description: Registry id required: true schema: type: string security: - authToken: [] + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/RegistryBody" + required: true responses: "204": - description: Success + description: Updated headers: X-Timestamp: description: FogController server timestamp schema: type: number - "404": - description: Not Found + "400": + description: Bad Request "401": description: Not Authorized + "404": + description: Invalid Registry Id "500": description: Internal Server Error - "/edgeResource/{name}": get: tags: - - Edge Resource - summary: Get Specific Edge Resource versions - operationId: getEdgeResourceVersions + - Registries + summary: Gets a registry + operationId: getRegistry parameters: - in: path - name: name - description: Edge Resource name + name: id + description: Registry id required: true schema: type: string @@ -3553,25 +2990,24 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/EdgeResourcesListResponse" + $ref: "#/components/schemas/RegistryResponse" "401": description: Not Authorized + "404": + description: Invalid Registry Id "500": description: Internal Server Error - /edgeResource: + /user/login: post: tags: - - Edge Resource - summary: Create Specific Edge Resource - operationId: postEdgeResource - security: - - authToken: [] + - User + summary: Login + operationId: login requestBody: content: application/json: schema: - $ref: "#/components/schemas/EdgeResourceCreateSchema" - description: Updated profile data + $ref: "#/components/schemas/LoginRequest" required: true responses: "200": @@ -3584,92 +3020,76 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/EdgeResourceCreateResponse" + $ref: "#/components/schemas/LoginSuccessResponse" + "400": + description: bad request "401": - description: Not Authorized - "500": - description: Internal Server Error - "/edgeResource/{name}/{version}/link": + description: incorrect credentials + /user/refresh: post: tags: - - Edge Resource - summary: Attach Edge Resource to Agent - operationId: postEdgeResourceLink - parameters: - - in: path - name: name - description: Edge Resource Name - required: true - schema: - type: string - - in: path - name: version - description: Edge Resource Version - required: true - schema: - type: string - security: - - authToken: [] + - User + summary: Refresh accessToken with refreshToken + operationId: refresh requestBody: content: application/json: schema: - $ref: "#/components/schemas/EdgeResourceLinkSchema" - description: Agent informations + $ref: "#/components/schemas/RefreshRequest" required: true responses: - "204": + "200": description: Success headers: X-Timestamp: description: FogController server timestamp schema: type: number + content: + application/json: + schema: + $ref: "#/components/schemas/RefreshSuccessResponse" + "400": + description: bad request + "401": + description: incorrect credentials + /user/logout: + post: + tags: + - User + summary: Logout + operationId: logout + security: + - authToken: [] + responses: + "204": + description: Success "401": description: Not Authorized - "404": - description: Not Found "500": description: Internal Server Error - delete: + /user/profile: + get: tags: - - Edge Resource - summary: Detach Edge Resource from Agent - operationId: deleteEdgeResourceLink - parameters: - - in: path - name: name - description: Edge Resource Name - required: true - schema: - type: string - - in: path - name: version - description: Edge Resource Version - required: true - schema: - type: string + - User + summary: Get current user profile data + operationId: getUserProfile security: - authToken: [] - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/EdgeResourceLinkSchema" - description: Agent informations - required: true responses: - "204": + "200": description: Success headers: X-Timestamp: description: FogController server timestamp schema: type: number + content: + application/json: + schema: + $ref: "#/components/schemas/UserProfileDetailsResponse" "401": description: Not Authorized - "404": - description: Not Found "500": description: Internal Server Error /secrets: @@ -6454,10 +5874,6 @@ tags: description: Manage your microservices - name: NATS description: Manage NATS operator, hub, accounts, users, and rules - - name: Edge Resource - description: Manage your Edge Resources - - name: Diagnostics - description: Diagnostic your microservices - name: Tunnel description: Manage ssh tunnels - name: Agent @@ -6516,13 +5932,6 @@ components: schema: $ref: "#/components/schemas/ApplicationTemplateCreateRequest" required: true - NewFlowRequest: - content: - application/json: - schema: - $ref: "#/components/schemas/NewFlowRequest" - description: New Flow Info - required: true schemas: EventRecord: type: object @@ -6624,100 +6033,6 @@ components: - deletedCount - deletedAt - deletedAll - EdgeResourcesListResponse: - type: object - properties: - edgeResources: - type: array - items: - type: object - properties: - id: - type: number - name: - type: string - description: - type: string - version: - type: string - interfaceProtocol: - type: string - display: - $ref: '#/components/schemas/EdgeResourceDisplay' - EdgeResourceGetResponse: - type: object - properties: - id: - type: number - name: - type: string - description: - type: string - version: - type: string - interfaceProtocol: - type: string - display: - $ref: '#/components/schemas/EdgeResourceDisplay' - interface: - $ref: '#/components/schemas/EdgeResourceHTTPInterface' - custom: - type: object - EdgeResourceDisplay: - type: object - properties: - color: - type: string - icon: - type: string - name: - type: string - EdgeResourceHTTPInterface: - type: object - properties: - endpoints: - type: array - items: - type: object - properties: - name: - type: number - description: - type: number - method: - type: string - url: - type: string - requestType: - type: string - responseType: - type: string - requestPayloadExample: - type: string - responsePayloadExample: - type: string - EdgeResourceCreateSchema: - type: object - properties: - name: - type: string - description: - type: string - version: - type: string - interfaceProtocol: - type: string - display: - $ref: '#/components/schemas/EdgeResourceDisplay' - interface: - $ref: '#/components/schemas/EdgeResourceHTTPInterface' - EdgeResourceCreateResponse: - $ref: '#/components/schemas/EdgeResourceGetResponse' - EdgeResourceLinkSchema: - type: object - properties: - uuid: - type: string ApplicationCreateFromTemplateRequest: type: object properties: @@ -7409,10 +6724,6 @@ components: type: boolean tunnel: type: boolean - diagnostics: - type: boolean - isImageSnapshot: - type: boolean prune: type: boolean routerChanged: @@ -7612,47 +6923,6 @@ components: type: string closed: type: boolean - IOFogNodeStraceResponse: - type: object - properties: - straceValues: - type: array - items: - $ref: "#/components/schemas/MicroserviceStrace" - MicroserviceStrace: - type: object - properties: - microserviceUuid: - type: string - straceRun: - type: boolean - IOFogNodeStraceBuffer: - type: object - properties: - straceData: - type: array - items: - $ref: "#/components/schemas/MicroserviceStraceBuffer" - MicroserviceStraceBuffer: - type: object - properties: - microserviceUuid: - type: string - buffer: - type: string - MicroserviceStraceFTPBody: - type: object - properties: - ftpHost: - type: string - ftpPort: - type: number - ftpUser: - type: string - ftpPass: - type: string - ftpDestDir: - type: string AgentMicroservicesListResponse: type: object properties: @@ -7689,8 +6959,6 @@ components: type: array items: $ref: "#/components/schemas/VolumeMapping" - imageSnapshot: - type: string delete: type: boolean deleteWithCleanUp: @@ -7982,33 +7250,6 @@ components: $ref: "#/components/schemas/InfoTypeResponse" configExample: type: string - GetFlowsResponse: - type: object - properties: - flows: - type: array - items: - $ref: "#/components/schemas/FlowInfoResponse" - FlowInfoResponse: - type: object - properties: - id: - type: string - name: - type: string - description: - type: string - isActivated: - type: boolean - NewFlowRequest: - type: object - properties: - name: - type: string - description: - type: string - isActivated: - type: boolean GetMicroservicesResponse: type: object properties: @@ -8721,20 +7962,6 @@ components: properties: info: type: string - ImageSnapshotResponse: - type: object - required: - - uuid - properties: - uuid: - type: string - ImageSnapshotRequest: - type: object - required: - - upstream - properties: - upstream: - type: string PostTrackingRequest: type: array items: diff --git a/src/config/rbac-resources.yaml b/src/config/rbac-resources.yaml index 19116999..52f540e7 100644 --- a/src/config/rbac-resources.yaml +++ b/src/config/rbac-resources.yaml @@ -64,17 +64,6 @@ resources: methods: PATCH: [patch] resourceNameParam: uuid - - path: /api/v3/microservices/:uuid/image-snapshot - methods: - GET: [get] - POST: [create] - resourceNameParam: uuid - - path: /api/v3/microservices/:uuid/strace - methods: - GET: [get] - PATCH: [patch] - PUT: [update] - resourceNameParam: uuid # System Microservices systemMicroservices: @@ -394,21 +383,6 @@ resources: PATCH: [patch] resourceNameParam: name - # Flows - flows: - basePath: /api/v3/flow - routes: - - path: /api/v3/flow - methods: - GET: [list] - POST: [create] - - path: /api/v3/flow/:id - methods: - GET: [get] - PATCH: [patch] - DELETE: [delete] - resourceNameParam: id - # Catalog catalog: basePath: /api/v3/catalog @@ -560,32 +534,6 @@ resources: methods: POST: [create] - # Edge Resources - edgeResources: - basePath: /api/v3/edgeResource - routes: - - path: /api/v3/edgeResources - methods: - GET: [list] - - path: /api/v3/edgeResource/:name/:version - methods: - GET: [get] - PUT: [update] - DELETE: [delete] - resourceNameParam: name - - path: /api/v3/edgeResource/:name - methods: - GET: [get] - resourceNameParam: name - - path: /api/v3/edgeResource - methods: - POST: [create] - - path: /api/v3/edgeResource/:name/:version/link - methods: - POST: [patch] - DELETE: [patch] - resourceNameParam: name - # Application Templates applicationTemplates: basePath: /api/v3/applicationTemplate @@ -615,9 +563,6 @@ resources: capabilities: basePath: /api/v3/capabilities routes: - - path: /api/v3/capabilities/edgeResources - methods: - HEAD: [get] - path: /api/v3/capabilities/applicationTemplates methods: HEAD: [get] @@ -634,22 +579,6 @@ resources: GET: [list] DELETE: [delete] - # Diagnostics - diagnostics: - basePath: /api/v3/microservices - routes: - - path: /api/v3/microservices/:uuid/image-snapshot - methods: - GET: [get] - POST: [create] - resourceNameParam: uuid - - path: /api/v3/microservices/:uuid/strace - methods: - GET: [get] - PATCH: [patch] - PUT: [update] - resourceNameParam: uuid - # Agent endpoints (for agent-to-controller communication) agent: basePath: /api/v3/agent @@ -674,9 +603,6 @@ resources: - path: /api/v3/agent/status methods: PUT: [update] - - path: /api/v3/agent/edgeResources - methods: - GET: [list] - path: /api/v3/agent/volumeMounts methods: GET: [list] @@ -693,10 +619,6 @@ resources: - path: /api/v3/agent/tunnel methods: GET: [get] - - path: /api/v3/agent/strace - methods: - GET: [get] - PUT: [update] - path: /api/v3/agent/version methods: GET: [get] @@ -709,10 +631,6 @@ resources: - path: /api/v3/agent/delete-node methods: DELETE: [delete] - - path: /api/v3/agent/image-snapshot - methods: - GET: [get] - PUT: [update] - path: /api/v3/agent/cert methods: GET: [get] diff --git a/src/config/rbac-system-roles.js b/src/config/rbac-system-roles.js index b2a93c0c..60b252e0 100644 --- a/src/config/rbac-system-roles.js +++ b/src/config/rbac-system-roles.js @@ -48,7 +48,7 @@ module.exports = { rules: [ { apiGroups: [''], - resources: ['microservices', 'systemMicroservices', 'fogs', 'applications', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'flows', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'tunnels', 'certificates', 'edgeResources', 'capabilities', 'diagnostics', 'serviceAccounts', 'events', 'users', 'config', 'controller', 'execSessions', 'systemExecSessions', 'logs', 'systemLogs'], + resources: ['microservices', 'systemMicroservices', 'fogs', 'applications', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'tunnels', 'certificates', 'capabilities', 'serviceAccounts', 'events', 'users', 'config', 'controller', 'execSessions', 'systemExecSessions', 'logs', 'systemLogs'], verbs: ['*'] }, { @@ -68,7 +68,7 @@ module.exports = { rules: [ { apiGroups: [''], - resources: ['microservices', 'applications', 'applicationTemplates', 'services', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'flows', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'certificates', 'edgeResources', 'capabilities', 'diagnostics', 'serviceAccounts', 'controller', 'execSessions', 'logs'], + resources: ['microservices', 'applications', 'applicationTemplates', 'services', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'certificates', 'capabilities', 'serviceAccounts', 'controller', 'execSessions', 'logs'], verbs: ['get', 'list', 'create', 'update', 'patch', 'delete'] }, { @@ -88,7 +88,7 @@ module.exports = { rules: [ { apiGroups: [''], - resources: ['microservices', 'fogs', 'applications', 'systemMicroservices', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsOperator', 'natsHub', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'flows', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'certificates', 'edgeResources', 'capabilities', 'diagnostics', 'serviceAccounts', 'config', 'controller', 'roles', 'roleBindings'], + resources: ['microservices', 'fogs', 'applications', 'systemMicroservices', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsOperator', 'natsHub', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'certificates', 'capabilities', 'serviceAccounts', 'config', 'controller', 'roles', 'roleBindings'], verbs: ['get', 'list'] } ] diff --git a/src/controllers/agent-controller.js b/src/controllers/agent-controller.js index e1aff0c2..ecbee291 100644 --- a/src/controllers/agent-controller.js +++ b/src/controllers/agent-controller.js @@ -60,10 +60,6 @@ const getAgentMicroservicesEndPoint = async function (req, fog) { return AgentService.getAgentMicroservices(fog) } -const getAgentLinkedEdgeResourcesEndpoint = async function (req, fog) { - return { edgeResources: await AgentService.getAgentLinkedEdgeResources(fog) } -} - const getAgentLinkedVolumeMountsEndpoint = async function (req, fog) { return { volumeMounts: await AgentService.getAgentLinkedVolumeMounts(fog) } } @@ -86,16 +82,6 @@ const getAgentTunnelEndPoint = async function (req, fog) { return AgentService.getAgentTunnel(fog) } -const getAgentStraceEndPoint = async function (req, fog) { - return AgentService.getAgentStrace(fog) -} - -const updateAgentStraceEndPoint = async function (req, fog) { - const straceData = req.body - - return AgentService.updateAgentStrace(straceData, fog) -} - const getAgentChangeVersionCommandEndPoint = async function (req, fog) { return AgentService.getAgentChangeVersionCommand(fog) } @@ -116,14 +102,6 @@ const deleteNodeEndPoint = async function (req, fog) { return AgentService.deleteNode(fog) } -const getImageSnapshotEndPoint = async function (req, fog) { - return AgentService.getImageSnapshot(fog) -} - -const putImageSnapshotEndPoint = async function (req, fog) { - return AgentService.putImageSnapshot(req, fog) -} - const getControllerCAEndPoint = async function (req, fog) { return AgentService.getControllerCA(fog) } @@ -140,16 +118,11 @@ module.exports = { getAgentMicroserviceEndPoint: AuthDecorator.checkFogToken(getAgentMicroserviceEndPoint), getAgentRegistriesEndPoint: AuthDecorator.checkFogToken(getAgentRegistriesEndPoint), getAgentTunnelEndPoint: AuthDecorator.checkFogToken(getAgentTunnelEndPoint), - getAgentStraceEndPoint: AuthDecorator.checkFogToken(getAgentStraceEndPoint), - updateAgentStraceEndPoint: AuthDecorator.checkFogToken(updateAgentStraceEndPoint), getAgentChangeVersionCommandEndPoint: AuthDecorator.checkFogToken(getAgentChangeVersionCommandEndPoint), updateHalHardwareInfoEndPoint: AuthDecorator.checkFogToken(updateHalHardwareInfoEndPoint), updateHalUsbInfoEndPoint: AuthDecorator.checkFogToken(updateHalUsbInfoEndPoint), deleteNodeEndPoint: AuthDecorator.checkFogToken(deleteNodeEndPoint), - getImageSnapshotEndPoint: AuthDecorator.checkFogToken(getImageSnapshotEndPoint), - putImageSnapshotEndPoint: AuthDecorator.checkFogToken(putImageSnapshotEndPoint), resetAgentConfigChangesEndPoint: AuthDecorator.checkFogToken(resetAgentConfigChangesEndPoint), - getAgentLinkedEdgeResourcesEndpoint: AuthDecorator.checkFogToken(getAgentLinkedEdgeResourcesEndpoint), getAgentLinkedVolumeMountsEndpoint: AuthDecorator.checkFogToken(getAgentLinkedVolumeMountsEndpoint), getControllerCAEndPoint: AuthDecorator.checkFogToken(getControllerCAEndPoint), getAgentLogSessionsEndPoint: AuthDecorator.checkFogToken(getAgentLogSessionsEndPoint) diff --git a/src/routes/agent.js b/src/routes/agent.js index 986cf3c0..de9ee1f1 100644 --- a/src/routes/agent.js +++ b/src/routes/agent.js @@ -250,31 +250,6 @@ module.exports = [ logger.apiRes({ req: req, res: res, responseObject: responseObject }) } }, - { - method: 'get', - path: '/api/v3/agent/edgeResources', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - } - ] - - const getAgentLinkedEdgeResourcesEndpoint = ResponseDecorator.handleErrors(AgentController.getAgentLinkedEdgeResourcesEndpoint, - successCode, errorCodes) - const responseObject = await getAgentLinkedEdgeResourcesEndpoint(req) - - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, res: res, responseObject: responseObject }) - } - }, { method: 'get', path: '/api/v3/agent/volumeMounts', @@ -408,68 +383,6 @@ module.exports = [ logger.apiRes({ req: req, res: res, responseObject: responseObject }) } }, - { - method: 'get', - path: '/api/v3/agent/strace', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - }, - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - } - ] - - const getAgentStraceEndPoint = ResponseDecorator.handleErrors(AgentController.getAgentStraceEndPoint, - successCode, errorCodes) - const responseObject = await getAgentStraceEndPoint(req) - - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, res: res, responseObject: responseObject }) - } - }, - { - method: 'put', - path: '/api/v3/agent/strace', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_NO_CONTENT - const errorCodes = [ - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - }, - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - const updateAgentStraceEndPoint = ResponseDecorator.handleErrors(AgentController.updateAgentStraceEndPoint, - successCode, errorCodes) - const responseObject = await updateAgentStraceEndPoint(req) - - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, res: res, responseObject: responseObject }) - } - }, { method: 'get', path: '/api/v3/agent/version', @@ -582,64 +495,6 @@ module.exports = [ logger.apiRes({ req: req, res: res, responseObject: responseObject }) } }, - { - method: 'get', - path: '/api/v3/agent/image-snapshot', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - const getImageSnapshotEndPoint = ResponseDecorator.handleErrors(AgentController.getImageSnapshotEndPoint, - successCode, errorCodes) - const responseObject = await getImageSnapshotEndPoint(req) - - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, res: res, responseObject: responseObject }) - } - }, - { - method: 'put', - path: '/api/v3/agent/image-snapshot', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - const putImageSnapshotEndPoint = ResponseDecorator.handleErrors(AgentController.putImageSnapshotEndPoint, - successCode, errorCodes) - const responseObject = await putImageSnapshotEndPoint(req) - - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, res: res, responseObject: responseObject }) - } - }, { method: 'get', path: '/api/v3/agent/cert', diff --git a/src/services/agent-service.js b/src/services/agent-service.js index b5e494f0..6125c525 100644 --- a/src/services/agent-service.js +++ b/src/services/agent-service.js @@ -12,9 +12,7 @@ */ const config = require('../config') -const path = require('path') const fs = require('fs') -const formidable = require('formidable') // const Sequelize = require('sequelize') const moment = require('moment') // const Op = Sequelize.Op @@ -26,7 +24,6 @@ const FogManager = require('../data/managers/iofog-manager') const FogKeyService = require('../services/iofog-key-service') const ChangeTrackingService = require('./change-tracking-service') const FogVersionCommandManager = require('../data/managers/iofog-version-command-manager') -const StraceManager = require('../data/managers/strace-manager') const RegistryManager = require('../data/managers/registry-manager') const MicroserviceStatusManager = require('../data/managers/microservice-status-manager') const MicroserviceExecStatusManager = require('../data/managers/microservice-exec-status-manager') @@ -42,7 +39,6 @@ const TunnelManager = require('../data/managers/tunnel-manager') const MicroserviceManager = require('../data/managers/microservice-manager') const MicroserviceService = require('../services/microservices-service') const ApplicationManager = require('../data/managers/application-manager') -const EdgeResourceService = require('./edge-resource-service') const constants = require('../helpers/constants') const SecretManager = require('../data/managers/secret-manager') const ConfigMapManager = require('../data/managers/config-map-manager') @@ -50,9 +46,8 @@ const MicroserviceLogStatusManager = require('../data/managers/microservice-log- const FogLogStatusManager = require('../data/managers/fog-log-status-manager') const RbacRoleManager = require('../data/managers/rbac-role-manager') -const IncomingForm = formidable.IncomingForm const CHANGE_TRACKING_DEFAULT = {} -const CHANGE_TRACKING_KEYS = ['config', 'version', 'reboot', 'deleteNode', 'microserviceList', 'microserviceConfig', 'registries', 'tunnel', 'diagnostics', 'isImageSnapshot', 'prune', 'routerChanged', 'linkedEdgeResources', 'volumeMounts', 'execSessions', 'microserviceLogs', 'fogLogs'] +const CHANGE_TRACKING_KEYS = ['config', 'version', 'reboot', 'deleteNode', 'microserviceList', 'microserviceConfig', 'registries', 'tunnel', 'prune', 'routerChanged', 'volumeMounts', 'execSessions', 'microserviceLogs', 'fogLogs'] for (const key of CHANGE_TRACKING_KEYS) { CHANGE_TRACKING_DEFAULT[key] = false } @@ -467,7 +462,6 @@ const getAgentMicroservices = async function (fog, transaction) { registryId, portMappings: microservice.ports, volumeMappings: microservice.volumeMappings, - imageSnapshot: microservice.imageSnapshot, delete: microservice.delete, deleteWithCleanup: microservice.deleteWithCleanup, env, @@ -509,30 +503,6 @@ const getAgentMicroservices = async function (fog, transaction) { } } -const getAgentLinkedEdgeResources = async function (fog, transaction) { - const edgeResources = [] - const resourceAttributes = [ - 'id', - 'interfaceId', - 'name', - 'version', - 'description', - 'interfaceProtocol', - 'displayName', - 'displayIcon', - 'displayColor', - 'custom' - ] - const resources = await fog.getEdgeResources({ attributes: resourceAttributes }) - for (const resource of resources) { - const intrface = await EdgeResourceService.getInterface(resource, transaction) - // Transform Sequelize objects into plain JSON objects - const resourceObject = { ...resource.toJSON(), interface: intrface.toJSON() } - edgeResources.push(EdgeResourceService.buildGetObject(resourceObject)) - } - return edgeResources -} - const getAgentMicroservice = async function (microserviceUuid, fog, transaction) { const microservice = await MicroserviceManager.findOneWithDependencies({ uuid: microserviceUuid, @@ -568,38 +538,6 @@ const getAgentTunnel = async function (fog, transaction) { } } -const getAgentStrace = async function (fog, transaction) { - const fogWithStrace = await FogManager.findFogStraces({ - uuid: fog.uuid - }, transaction) - - if (!fogWithStrace) { - throw new Errors.NotFoundError(ErrorMessages.STRACE_NOT_FOUND) - } - - const straceArr = [] - for (const msData of fogWithStrace.microservice) { - straceArr.push({ - microserviceUuid: msData.strace.microserviceUuid, - straceRun: msData.strace.straceRun - }) - } - - return { - straceValues: straceArr - } -} - -const updateAgentStrace = async function (straceData, fog, transaction) { - await Validator.validate(straceData, Validator.schemas.updateAgentStrace) - - for (const strace of straceData.straceData) { - const microserviceUuid = strace.microserviceUuid - const buffer = strace.buffer - await StraceManager.pushBufferByMicroserviceUuid(microserviceUuid, buffer, transaction) - } -} - const getAgentChangeVersionCommand = async function (fog, transaction) { const versionCommand = await FogVersionCommandManager.findOne({ iofogUuid: fog.uuid @@ -645,68 +583,6 @@ const deleteNode = async function (fog, transaction) { }, transaction) } -const getImageSnapshot = async function (fog, transaction) { - const microservice = await MicroserviceManager.findOne({ - iofogUuid: fog.uuid, - imageSnapshot: 'get_image' - }, transaction) - if (!microservice) { - throw new Errors.NotFoundError(ErrorMessages.IMAGE_SNAPSHOT_NOT_FOUND) - } - - return { - uuid: microservice.uuid - } -} - -const putImageSnapshot = async function (req, fog, transaction) { - const opts = { - maxFieldsSize: 500 * 1024 * 1024, - maxFileSize: 500 * 1024 * 1024 - } - if (!req.headers['content-type'].includes('multipart/form-data')) { - throw new Errors.ValidationError(ErrorMessages.INVALID_CONTENT_TYPE) - } - - const form = new IncomingForm(opts) - form.uploadDir = path.join(global.appRoot, '../') + 'data' - if (!fs.existsSync(form.uploadDir)) { - fs.mkdirSync(form.uploadDir) - } - await _saveSnapShot(req, form, fog, transaction) - return {} -} - -const _saveSnapShot = function (req, form, fog, transaction) { - return new Promise((resolve, reject) => { - form.parse(req, async function (error, fields, files) { - if (error) { - reject(new Errors.ValidationError(ErrorMessages.UPLOADED_FILE_NOT_FOUND)) - return - } - const file = files['upstream'] - if (file === undefined) { - reject(new Errors.ValidationError(ErrorMessages.UPLOADED_FILE_NOT_FOUND)) - return - } - - const filePath = file['path'] - - const absolutePath = path.resolve(filePath) - fs.renameSync(absolutePath, absolutePath + '.tar.gz') - - await MicroserviceManager.update({ - iofogUuid: fog.uuid, - imageSnapshot: 'get_image' - }, { - imageSnapshot: absolutePath + '.tar.gz' - }, transaction) - - resolve() - }) - }) -} - async function _checkMicroservicesFogType (fog, archId, transaction) { const where = { iofogUuid: fog.uuid @@ -914,15 +790,10 @@ module.exports = { getAgentMicroservice: TransactionDecorator.generateTransaction(getAgentMicroservice), getAgentRegistries: TransactionDecorator.generateTransaction(getAgentRegistries), getAgentTunnel: TransactionDecorator.generateTransaction(getAgentTunnel), - getAgentStrace: TransactionDecorator.generateTransaction(getAgentStrace), - updateAgentStrace: TransactionDecorator.generateTransaction(updateAgentStrace), getAgentChangeVersionCommand: TransactionDecorator.generateTransaction(getAgentChangeVersionCommand), updateHalHardwareInfo: TransactionDecorator.generateTransaction(updateHalHardwareInfo), updateHalUsbInfo: TransactionDecorator.generateTransaction(updateHalUsbInfo), deleteNode: TransactionDecorator.generateTransaction(deleteNode), - getImageSnapshot: TransactionDecorator.generateTransaction(getImageSnapshot), - putImageSnapshot: TransactionDecorator.generateTransaction(putImageSnapshot), - getAgentLinkedEdgeResources: TransactionDecorator.generateTransaction(getAgentLinkedEdgeResources), getAgentLinkedVolumeMounts: TransactionDecorator.generateTransaction(getAgentLinkedVolumeMounts), getControllerCA: TransactionDecorator.generateTransaction(getControllerCA), getAgentLogSessions: TransactionDecorator.generateTransaction(getAgentLogSessions) diff --git a/src/services/change-tracking-service.js b/src/services/change-tracking-service.js index 221b8f66..d3536435 100644 --- a/src/services/change-tracking-service.js +++ b/src/services/change-tracking-service.js @@ -25,19 +25,11 @@ const events = Object.freeze({ microserviceConfig: false, registries: false, tunnel: false, - diagnostics: false, - isImageSnapshot: false, prune: false, routerChanged: false, volumeMounts: false, execSessions: false }, - diagnostics: { - diagnostics: true - }, - imageSnapshot: { - isImageSnapshot: true - }, microserviceFull: { microserviceConfig: true, microserviceList: true @@ -52,9 +44,6 @@ const events = Object.freeze({ microserviceConfig: { microserviceConfig: true }, - edgeResources: { - linkedEdgeResources: true - }, version: { version: true }, diff --git a/src/services/event-service.js b/src/services/event-service.js index 78f5ee76..087694d9 100644 --- a/src/services/event-service.js +++ b/src/services/event-service.js @@ -42,9 +42,6 @@ function extractResourceType (path) { { pattern: /^\/api\/v3\/registries/, type: 'registry' }, { pattern: /^\/api\/v3\/volumeMounts/, type: 'volumeMount' }, { pattern: /^\/api\/v3\/configMaps/, type: 'configMap' }, - { pattern: /^\/api\/v3\/edgeResources/, type: 'edgeResource' }, - { pattern: /^\/api\/v3\/diagnostics/, type: 'diagnostics' }, - { pattern: /^\/api\/v3\/flows/, type: 'application' }, { pattern: /^\/api\/v3\/applicationTemplates/, type: 'applicationTemplate' }, { pattern: /^\/api\/v3\/catalog/, type: 'catalog' }, { pattern: /^\/api\/v3\/controller/, type: 'controller' }, From 598b3567f3859cc2e80c355e25bee4602d968566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 04:17:54 +0300 Subject: [PATCH 16/75] Update unit tests and Postman collection for removed legacy APIs. Remove diagnostics controller/service tests and prune Postman folders for flow, edge resources, diagnostics, and strace. --- test/postman_collection.json | 6397 +++-------------- test/src/controllers/agent-controller.test.js | 151 - .../diagnostics-controller.test.js | 235 - .../rvaluesVarSubstitionMiddleware.test.js | 79 - test/src/services/agent-service.test.js | 189 +- test/src/services/diagnostic-service.test.js | 524 -- test/src/services/iofog-service.test.js | 9 +- 7 files changed, 913 insertions(+), 6671 deletions(-) delete mode 100644 test/src/controllers/diagnostics-controller.test.js delete mode 100644 test/src/services/diagnostic-service.test.js diff --git a/test/postman_collection.json b/test/postman_collection.json index 2a975f60..0419d9fa 100644 --- a/test/postman_collection.json +++ b/test/postman_collection.json @@ -1119,48 +1119,6 @@ }, "response": [] }, - { - "name": "Get agent strace", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/strace", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "strace" - ] - } - }, - "response": [] - }, { "name": "Update hardware info", "event": [ @@ -1255,55 +1213,6 @@ }, "response": [] }, - { - "name": "Put agent strace", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === \"NotFoundError\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"straceData\": [\r\n {\r\n \"microserviceUuid\": \"abcdef\",\r\n \"buffer\": \"test\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/strace", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "strace" - ] - } - }, - "response": [] - }, { "name": "Update agent status", "event": [ @@ -1510,8 +1419,7 @@ "", "tests[\"Response validation passed\"] = data.hasOwnProperty('config') && data.hasOwnProperty('version') && data.hasOwnProperty('reboot')", "&& data.hasOwnProperty('deleteNode') && data.hasOwnProperty('microserviceList') && data.hasOwnProperty('microserviceConfig')", - "&& data.hasOwnProperty('routing') && data.hasOwnProperty('registries') && data.hasOwnProperty('tunnel') && data.hasOwnProperty('diagnostics')", - "&& data.hasOwnProperty('isImageSnapshot') && data.hasOwnProperty('prune');" + "&& data.hasOwnProperty('registries') && data.hasOwnProperty('tunnel') && data.hasOwnProperty('prune');" ], "type": "text/javascript" } @@ -4632,60 +4540,6 @@ }, "response": [] }, - { - "name": "Create Edge Resource", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = !!data.id;", - "", - "if (responseCode.code === 200) {", - " postman.setGlobalVariable(\"edge-resource-name\", data.name);", - " postman.setGlobalVariable(\"edge-resource-version\", data.version);", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"com.orange.smart-door\",\n \"version\": \"0.0.1\",\n \"description\": \"Orange Smart Door\",\n \"display\": {\n \"name\": \"Smart Door\",\n \"icon\": \"help\",\n \"color\": \"#ff0000\"\n },\n \"interfaceProtocol\": \"https\",\n \"interface\": {\n \"endpoints\": [\n {\n \"name\": \"liveness\",\n \"url\": \"https://localhost:91121\",\n \"method\": \"GET\"\n },\n {\n \"name\": \"version\",\n \"url\": \"https://localhost:91121/version\",\n \"method\": \"GET\"\n }\n ]\n },\n \"orchestrationTags\": [\n \"orange\",\n \"smart-door\"\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/edgeResource", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource" - ] - } - }, - "response": [] - }, { "name": "New Application", "event": [ @@ -4773,7 +4627,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"application-msvc-name\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true,\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-msvc-name\",\n \"iofogUuid\": \"{{node-id-2}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 1881,\n \"external\": 1882\n }\n ],\n \"routes\": [],\n \"env\": [\n { \n \"key\": \"selfname\",\n \"value\": \"{{ self.name | upcase }}\"\n },\n {\n \"key\": \"sharedToken\",\n \"value\": \"sekrittoken\"\n },\n {\n \"key\": \"http_proxy\",\n \"value\": \"http://myproxy:8080/\"\n },\n {\n \"key\": \"https_proxy\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\" , \\\"http_proxy\\\" | first | map: \\\"value\\\" | first }}\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-msvc-name\",\n \"iofogUuid\": \"{{node-id-2}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n { \n \"key\": \"selfname\",\n \"value\": \"{{ self.name | upcase }}\"\n },\n {\n \"key\": \"sharedToken\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\", \\\"sharedToken\\\" | first | map: \\\"value\\\" | first }}\"\n },\n {\n \"key\": \"http_proxy\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\" , \\\"http_proxy\\\" | first | map: \\\"value\\\" | first }}\"\n },\n {\n \"key\": \"https_proxy\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\" , \\\"http_proxy\\\" | first | map: \\\"value\\\" | first }}\"\n },\n {\n \"key\": \"rulesengineHOST\",\n \"value\": \"{% assign curmsvc= self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first %}{{ curmsvc | findMicroserviceAgent | map: \\\"host\\\" | first }}\"\n },\n {\n \"key\": \"rulesenginePORT\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"ports\\\" | first | map: \\\"external\\\" | first | toString }}\"\n },\n {\n \"key\": \"redisHost\",\n \"value\": \"{% assign redisApp = \\\"application-name-1\\\" | findApplication %}{% assign redismsvc = redisApp.microservices | where: \\\"name\\\", \\\"redistest\\\" | first %}{{ redismsvc | findMicroserviceAgent | map: \\\"host\\\"}}:{{ redismsvc | map: \\\"ports\\\" | first | first | map: \\\"external\\\" | first | toString }}\"\n },\n {\n \"key\": \"edgeResLiveness\",\n \"value\": \"{{ \\\"com.orange.smart-door\\\" | findEdgeResource: \\\"0.0.1\\\" | map: \\\"interface\\\" | map: \\\"endpoints\\\" | first | where: \\\"name\\\", \\\"liveness\\\" | first | map: \\\"url\\\" | first }}\"\n },\n {\n \"key\": \"edgeResVersion\",\n \"value\": \"{{ \\\"com.orange.smart-door\\\" | findEdgeResource | map: \\\"interface\\\" | map: \\\"endpoints\\\" | first | where: \\\"name\\\", \\\"version\\\" | first | map: \\\"url\\\" | first }}\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-3\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-msvc-name\",\n \"iofogUuid\": \"{{node-id-2}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ],\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-2\",\n \"name\": \"m1-2\"\n }\n ]\n}" + "raw": "{\n \"name\": \"application-msvc-name\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true,\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-msvc-name\",\n \"iofogUuid\": \"{{node-id-2}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 1881,\n \"external\": 1882\n }\n ],\n \"routes\": [],\n \"env\": [\n { \n \"key\": \"selfname\",\n \"value\": \"{{ self.name | upcase }}\"\n },\n {\n \"key\": \"sharedToken\",\n \"value\": \"sekrittoken\"\n },\n {\n \"key\": \"http_proxy\",\n \"value\": \"http://myproxy:8080/\"\n },\n {\n \"key\": \"https_proxy\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\" , \\\"http_proxy\\\" | first | map: \\\"value\\\" | first }}\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-msvc-name\",\n \"iofogUuid\": \"{{node-id-2}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n { \n \"key\": \"selfname\",\n \"value\": \"{{ self.name | upcase }}\"\n },\n {\n \"key\": \"sharedToken\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\", \\\"sharedToken\\\" | first | map: \\\"value\\\" | first }}\"\n },\n {\n \"key\": \"http_proxy\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\" , \\\"http_proxy\\\" | first | map: \\\"value\\\" | first }}\"\n },\n {\n \"key\": \"https_proxy\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\" , \\\"http_proxy\\\" | first | map: \\\"value\\\" | first }}\"\n },\n {\n \"key\": \"rulesengineHOST\",\n \"value\": \"{% assign curmsvc= self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first %}{{ curmsvc | findMicroserviceAgent | map: \\\"host\\\" | first }}\"\n },\n {\n \"key\": \"rulesenginePORT\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"ports\\\" | first | map: \\\"external\\\" | first | toString }}\"\n },\n {\n \"key\": \"redisHost\",\n \"value\": \"{% assign redisApp = \\\"application-name-1\\\" | findApplication %}{% assign redismsvc = redisApp.microservices | where: \\\"name\\\", \\\"redistest\\\" | first %}{{ redismsvc | findMicroserviceAgent | map: \\\"host\\\"}}:{{ redismsvc | map: \\\"ports\\\" | first | first | map: \\\"external\\\" | first | toString }}\"\n }}\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-3\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-msvc-name\",\n \"iofogUuid\": \"{{node-id-2}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ],\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-2\",\n \"name\": \"m1-2\"\n }\n ]\n}" }, "url": { "raw": "{{host}}/api/v3/application", @@ -4821,8 +4675,6 @@ "", "tests[\"msv2 gets hostname from iofog agent for itself\"]=data.microservices[1].env[4].value === '1.2.3.5'", "tests[\"msv2 gets hostname from iofog agent for a service of another app\"]=data.microservices[1].env[6].value === '1.2.3.4:6379'", - "tests[\"msv2 gets edge Resource liveness endpoint\"]=data.microservices[1].env[7].value === 'https://localhost:91121'", - "tests[\"msv2 gets edge Resource version endpoint (without version for findEdgeResource)\"]=data.microservices[1].env[8].value === 'https://localhost:91121/version'", "", "" ], @@ -5264,53 +5116,6 @@ }, "response": [] }, - { - "name": "Delete Edge Resource", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}" - ] - } - }, - "response": [] - }, { "name": "Delete system node", "event": [ @@ -5517,7 +5322,7 @@ ] }, { - "name": "Legacy: Flow", + "name": "Catalog", "item": [ { "name": "Create user", @@ -5612,7 +5417,7 @@ "response": [] }, { - "name": "New Flow", + "name": "New Catalog Item", "event": [ { "listen": "test", @@ -5624,7 +5429,7 @@ "", "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", "", - "postman.setGlobalVariable(\"flow-id\", data.id);" + "postman.setGlobalVariable(\"item-id\", data.id);" ], "type": "text/javascript" } @@ -5645,24 +5450,25 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"flow-name\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true\n}" + "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}" }, "url": { - "raw": "{{host}}/api/v3/flow", + "raw": "{{host}}/api/v3/catalog/microservices", "host": [ "{{host}}" ], "path": [ "api", "v3", - "flow" + "catalog", + "microservices" ] } }, "response": [] }, { - "name": "Get Flows", + "name": "Get Catalog Items", "event": [ { "listen": "test", @@ -5672,7 +5478,7 @@ "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('flows') && data.flows.length > 0;" + "tests[\"Response validation passed\"] = data.hasOwnProperty('catalogItems');" ], "type": "text/javascript" } @@ -5692,21 +5498,22 @@ } ], "url": { - "raw": "{{host}}/api/v3/flow", + "raw": "{{host}}/api/v3/catalog/microservices", "host": [ "{{host}}" ], "path": [ "api", "v3", - "flow" + "catalog", + "microservices" ] } }, "response": [] }, { - "name": "Get Flow By Id", + "name": "Get Catalog Item By Id", "event": [ { "listen": "test", @@ -5716,7 +5523,9 @@ "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.hasOwnProperty('isActivated');" + "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.category && data.hasOwnProperty('configExample')", + "&& data.publisher && data.hasOwnProperty('diskRequired') && data.hasOwnProperty('ramRequired') && data.picture && data.hasOwnProperty('isPublic')", + "&& data.hasOwnProperty('registryId') && data.hasOwnProperty('images') && data.hasOwnProperty('inputType') && data.hasOwnProperty('outputType');" ], "type": "text/javascript" } @@ -5736,28 +5545,30 @@ } ], "url": { - "raw": "{{host}}/api/v3/flow/{{flow-id}}", + "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "flow", - "{{flow-id}}" + "catalog", + "microservices", + "{{item-id}}" ] } }, "response": [] }, { - "name": "Update Flow", + "name": "Update Catalog Item", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" + "tests[\"Status code is 204\"] = responseCode.code === 204;", + "" ], "type": "text/javascript" } @@ -5778,75 +5589,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"flow-name-22\",\n \"isSystem\": true,\n \"description\": \"Description\",\n \"isActivated\": true\n}" + "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}\n" }, "url": { - "raw": "{{host}}/api/v3/flow/{{flow-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "flow", - "{{flow-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Flows without system", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('flows') && data.flows.length === 0;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/flow", + "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "flow" + "catalog", + "microservices", + "{{item-id}}" ] } }, "response": [] }, { - "name": "Delete Flow", + "name": "Delete Catalog Item By Id", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" + "tests[\"Status code is 204\"] = responseCode.code === 204;", + "" ], "type": "text/javascript" } @@ -5870,15 +5639,16 @@ "raw": "" }, "url": { - "raw": "{{host}}/api/v3/flow/{{flow-id}}", + "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "flow", - "{{flow-id}}" + "catalog", + "microservices", + "{{item-id}}" ] } }, @@ -5929,7 +5699,7 @@ "response": [] } ], - "description": "Flow collection", + "description": "Catalog collection", "event": [ { "listen": "prerequest", @@ -5952,7 +5722,7 @@ ] }, { - "name": "Legacy: Microservices", + "name": "Tunnel", "item": [ { "name": "Create user", @@ -6132,7 +5902,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" + "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1,\n \"dockerPruningFrequency\": 35,\n \"availableDiskThreshold\": 95,\n \"logLevel\": \"INFO\"\n,\n \"host\": \"1.2.3.4\"\n}" }, "url": { "raw": "{{host}}/api/v3/iofog", @@ -6149,27 +5919,20 @@ "response": [] }, { - "name": "New Flow", + "name": "Open SSH Tunnel To ioFog Node", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"flow-id\", data.id);", - "postman.setGlobalVariable(\"flow-name\", data.name);" + "" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -6183,43 +5946,39 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"flow-name\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" + "raw": "{\n \"action\": \"open\"\n}" }, "url": { - "raw": "{{host}}/api/v3/flow", + "raw": "{{host}}/api/v3/iofog/{{node-id}}/tunnel", "host": [ "{{host}}" ], "path": [ "api", "v3", - "flow" + "iofog", + "{{node-id}}", + "tunnel" ] } }, "response": [] }, { - "name": "Second Flow", + "name": "Get Node SSH Tunnel Info", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"flow-id-2\", data.id);" + "" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "GET", "header": [ { "key": "Content-Type", @@ -6231,45 +5990,37 @@ "type": "text" } ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"flow-name-second\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, "url": { - "raw": "{{host}}/api/v3/flow", + "raw": "{{host}}/api/v3/iofog/{{node-id}}/tunnel", "host": [ "{{host}}" ], "path": [ "api", "v3", - "flow" + "iofog", + "{{node-id}}", + "tunnel" ] } }, "response": [] }, { - "name": "New Catalog Item", + "name": "Close SSH Tunnel To ioFog Node", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"item-id\", data.id);" + "" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -6277,50 +6028,46 @@ }, { "key": "Authorization", - "value": "{{user-token}}", - "type": "text" + "type": "text", + "value": "{{user-token}}" } ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}" + "raw": "{\n \"action\": \"close\"\n}" }, "url": { - "raw": "{{host}}/api/v3/catalog/microservices", + "raw": "{{host}}/api/v3/iofog/{{node-id}}/tunnel", "host": [ "{{host}}" ], "path": [ "api", "v3", - "catalog", - "microservices" + "iofog", + "{{node-id}}", + "tunnel" ] } }, "response": [] }, { - "name": "New Microservice", + "name": "Delete system node", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"ms-id\", data.uuid);" + "tests[\"Status code is 202\"] = responseCode.code === 202;", + "" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "DELETE", "header": [ { "key": "Content-Type", @@ -6328,47 +6075,45 @@ }, { "key": "Authorization", - "value": "{{user-token}}", - "type": "text" + "type": "text", + "value": "{{user-token}}" } ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"name2\",\n \"config\": \"string\",\n \"catalogItemId\": {{item-id}},\n \"flowId\": {{flow-id}},\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 1,\n \"external\": 1,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" + "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" }, "url": { - "raw": "{{host}}/api/v3/microservices", + "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices" + "iofog", + "{{system-node-id}}" ] } }, "response": [] }, { - "name": "New Microservice without catalog in second flow", + "name": "Delete Node", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;" + "tests[\"Status code is 202\"] = responseCode.code === 202;", + "" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "DELETE", "header": [ { "key": "Content-Type", @@ -6376,92 +6121,111 @@ }, { "key": "Authorization", - "value": "{{user-token}}", - "type": "text" + "type": "text", + "value": "{{user-token}}" } ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"namesec\",\n \"config\": \"string\",\n \"images\": [{\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 1,\n \"flowId\": {{flow-id-2}},\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" + "raw": "" }, "url": { - "raw": "{{host}}/api/v3/microservices", + "raw": "{{host}}/api/v3/iofog/{{node-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices" + "iofog", + "{{node-id}}" ] } }, "response": [] }, { - "name": "New Microservice without catalog", + "name": "Delete user", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"ms-no-catalog-id\", data.uuid);" + "tests[\"Status code is 204\"] = responseCode.code === 204;" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "DELETE", "header": [ { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "{{user-token}}" }, { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"name-without-catalog\",\n \"config\": \"string\",\n \"images\": [{\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 1,\n \"flowId\": {{flow-id}},\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 2,\n \"external\": 2,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" + "raw": "" }, "url": { - "raw": "{{host}}/api/v3/microservices", + "raw": "{{host}}/api/v3/user/profile", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices" + "user", + "profile" ] } }, "response": [] + } + ], + "description": "Tunnel collection", + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } }, { - "name": "New Route", + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + }, + { + "name": "Microservices", + "item": [ + { + "name": "Create user", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Status code is 201\"] = responseCode.code === 201;", "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response validation passed\"] = data.name;", - "", - "postman.setGlobalVariable(\"route-name\", data.name);" + "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" ], "type": "text/javascript" } @@ -6473,94 +6237,97 @@ { "key": "Content-Type", "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"route-name\",\n \"sourceMicroserviceUuid\": \"{{ms-no-catalog-id}}\",\n \"destMicroserviceUuid\": \"{{ms-id}}\"\n}\n" + "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" }, "url": { - "raw": "{{host}}/api/v3/routes", + "raw": "{{host}}/api/v3/user/signup", "host": [ "{{host}}" ], "path": [ "api", "v3", - "routes" + "user", + "signup" ] } }, "response": [] }, { - "name": "Update Route", + "name": "Login", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", + "tests[\"Status code is 200\"] = responseCode.code === 200;", "", - "postman.setGlobalVariable(\"route-name\", \"route-name-updated\");" + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.accessToken;", + "", + "", + "postman.setGlobalVariable(\"user-token\", data.accessToken);", + "" ], "type": "text/javascript" } } ], "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"route-name-updated\",\n \"sourceMicroserviceUuid\": \"{{ms-no-catalog-id}}\",\n \"destMicroserviceUuid\": \"{{ms-id}}\"\n}\n" + "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" }, "url": { - "raw": "{{host}}/api/v3/routes/{{flow-name}}/{{route-name}}", + "raw": "{{host}}/api/v3/user/login", "host": [ "{{host}}" ], "path": [ "api", "v3", - "routes", - "{{flow-name}}", - "{{route-name}}" + "user", + "login" ] } }, "response": [] }, { - "name": "Delete Route", + "name": "New System Node", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" + "tests[\"Status code is 201\"] = responseCode.code === 201;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.uuid;", + "", + "postman.setGlobalVariable(\"system-node-id\", data.uuid);", + "" ], "type": "text/javascript" } } ], "request": { - "method": "DELETE", + "method": "POST", "header": [ { "key": "Content-Type", @@ -6568,55 +6335,50 @@ }, { "key": "Authorization", - "value": "{{user-token}}", - "type": "text" + "type": "text", + "value": "{{user-token}}" } ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" }, "url": { - "raw": "{{host}}/api/v3/routes/{{flow-name}}/{{route-name}}", + "raw": "{{host}}/api/v3/iofog", "host": [ "{{host}}" ], "path": [ "api", "v3", - "routes", - "{{flow-name}}", - "{{route-name}}" + "iofog" ] } }, "response": [] }, { - "name": "Get All Microservices", + "name": "New Node", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Status code is 201\"] = responseCode.code === 201;", "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains three microservices\"] = data.microservices.length === 3;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", + "tests[\"Response validation passed\"] = data.uuid;", "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';" + "postman.setGlobalVariable(\"node-id\", data.uuid);", + "" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Content-Type", @@ -6624,53 +6386,49 @@ }, { "key": "Authorization", - "value": "{{user-token}}", - "type": "text" + "type": "text", + "value": "{{user-token}}" } ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" + }, "url": { - "raw": "{{host}}/api/v3/microservices", + "raw": "{{host}}/api/v3/iofog", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices" + "iofog" ] } }, "response": [] }, { - "name": "Get Microservices", + "name": "New Application", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Status code is 201\"] = responseCode.code === 201;", "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains two microservices\"] = data.microservices.length === 2;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", + "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", "", - "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", - "", - "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');" + "postman.setGlobalVariable(\"application-name\", data.name);" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Content-Type", @@ -6682,45 +6440,45 @@ "type": "text" } ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"application-name\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" + }, "url": { - "raw": "{{host}}/api/v3/microservices?flowId={{flow-id}}", + "raw": "{{host}}/api/v3/application", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices" - ], - "query": [ - { - "key": "flowId", - "value": "{{flow-id}}" - } + "application" ] } }, "response": [] }, { - "name": "Get Microservice By Id", + "name": "Second Application", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Status code is 201\"] = responseCode.code === 201;", "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" + "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", + "", + "postman.setGlobalVariable(\"application-name-2\", data.name);" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Content-Type", @@ -6732,40 +6490,45 @@ "type": "text" } ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"application-name-second\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" + }, "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}", + "raw": "{{host}}/api/v3/application", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices", - "{{ms-id}}" + "application" ] } }, "response": [] }, { - "name": "Get Microservice without catalog By Id", + "name": "New Catalog Item", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Status code is 201\"] = responseCode.code === 201;", "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" + "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", + "", + "postman.setGlobalVariable(\"item-id\", data.id);" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Content-Type", @@ -6777,36 +6540,47 @@ "type": "text" } ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}" + }, "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", + "raw": "{{host}}/api/v3/catalog/microservices", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices", - "{{ms-no-catalog-id}}" + "catalog", + "microservices" ] } }, "response": [] }, { - "name": "Update Microservice", + "name": "New Microservice", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" + "tests[\"Status code is 201\"] = responseCode.code === 201;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.uuid;", + "", + "postman.setGlobalVariable(\"ms-name\", data.name);", + "postman.setGlobalVariable(\"ms-id\", data.uuid);" ], "type": "text/javascript" } } ], "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", @@ -6820,38 +6594,41 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"name3\",\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" + "raw": "{\n \"name\": \"name2\",\n \"config\": \"string\",\n \"catalogItemId\": {{item-id}},\n \"application\": \"{{application-name}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 1,\n \"external\": 1,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" }, "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}", + "raw": "{{host}}/api/v3/microservices", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices", - "{{ms-id}}" + "microservices" ] } }, "response": [] }, { - "name": "Update Microservice without catalog item", + "name": "New Microservice without catalog in second application", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" + "tests[\"Status code is 201\"] = responseCode.code === 201;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.uuid;" ], "type": "text/javascript" } } ], "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", @@ -6865,42 +6642,44 @@ ], "body": { "mode": "raw", - "raw": "{\n \"images\": [\n {\n \"containerImage\": \"hello-world-updated\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world-updated\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 2,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" + "raw": "{\n \"name\": \"namesec\",\n \"config\": \"string\",\n \"images\": [{\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" }, "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", + "raw": "{{host}}/api/v3/microservices", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices", - "{{ms-no-catalog-id}}" + "microservices" ] } }, "response": [] }, { - "name": "Get Updated Microservice without catalog By Id", + "name": "New Microservice without catalog", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Status code is 201\"] = responseCode.code === 201;", "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world-updated\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.registryId === 2 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" + "tests[\"Response validation passed\"] = data.uuid;", + "", + "postman.setGlobalVariable(\"ms-no-catalog-name\", data.name);", + "postman.setGlobalVariable(\"ms-no-catalog-id\", data.uuid);" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Content-Type", @@ -6912,36 +6691,45 @@ "type": "text" } ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"name-without-catalog\",\n \"config\": \"string\",\n \"images\": [{\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 1,\n \"application\": \"{{application-name}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 2,\n \"external\": 2,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" + }, "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", + "raw": "{{host}}/api/v3/microservices", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices", - "{{ms-no-catalog-id}}" + "microservices" ] } }, "response": [] }, { - "name": "Update Microservice without catalog item to give it a catalog item", + "name": "New Route", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.name;", + "", + "postman.setGlobalVariable(\"route-name\", data.name);" ], "type": "text/javascript" } } ], "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", @@ -6955,42 +6743,39 @@ ], "body": { "mode": "raw", - "raw": "{\n \"catalogItemId\": 14,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" + "raw": "{\n \"name\": \"route-name\",\n \"from\": \"{{ms-no-catalog-name}}\",\n \"to\": \"{{ms-name}}\",\n \"application\": \"{{application-name}}\"\n}\n" }, "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", + "raw": "{{host}}/api/v3/routes", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices", - "{{ms-no-catalog-id}}" + "routes" ] } }, "response": [] }, { - "name": "Get Updated Microservice with catalog By Id", + "name": "Update Route", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", + "tests[\"Status code is 204\"] = responseCode.code === 204;", "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 0 && data.hasOwnProperty('catalogItemId') && data.catalogItemId === 14 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" + "postman.setGlobalVariable(\"route-name\", \"route-name-updated\");" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -7002,23 +6787,28 @@ "type": "text" } ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"route-name-updated\",\n \"from\": \"{{ms-no-catalog-name}}\",\n \"to\": \"{{ms-name}}\",\n \"application\": \"{{application-name}}\"\n}\n" + }, "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", + "raw": "{{host}}/api/v3/routes/{{application-name}}/{{route-name}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices", - "{{ms-no-catalog-id}}" + "routes", + "{{application-name}}", + "{{route-name}}" ] } }, "response": [] }, { - "name": "Update Microservice without catalog item", + "name": "Delete Route", "event": [ { "listen": "test", @@ -7031,7 +6821,7 @@ } ], "request": { - "method": "PATCH", + "method": "DELETE", "header": [ { "key": "Content-Type", @@ -7045,25 +6835,26 @@ ], "body": { "mode": "raw", - "raw": "{\n \"images\": [\n {\n \"containerImage\": \"hello-world-updated\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world-updated\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 2,\n \"catalogItemId\": null,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" + "raw": "" }, "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", + "raw": "{{host}}/api/v3/routes/{{application-name}}/{{route-name}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices", - "{{ms-no-catalog-id}}" + "routes", + "{{application-name}}", + "{{route-name}}" ] } }, "response": [] }, { - "name": "Get Updated Microservice again without catalog By Id", + "name": "Get All Microservices", "event": [ { "listen": "test", @@ -7073,7 +6864,13 @@ "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world-updated\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.registryId === 2 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" + "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", + "", + "tests[\"Response contains three microservices\"] = data.microservices.length === 3;", + "", + "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", + "", + "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';" ], "type": "text/javascript" } @@ -7093,22 +6890,21 @@ } ], "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", + "raw": "{{host}}/api/v3/microservices", "host": [ "{{host}}" ], "path": [ "api", "v3", - "microservices", - "{{ms-no-catalog-id}}" + "microservices" ] } }, "response": [] }, { - "name": "Create a Route From Microservice to Receiver", + "name": "Get Microservices", "event": [ { "listen": "test", @@ -7118,16 +6914,24 @@ "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response validation passed\"] = data.name", + "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", "", - "postman.setGlobalVariable(\"route-id\", data.name);" + "tests[\"Response contains two microservices\"] = data.microservices.length === 2;", + "", + "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", + "", + "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", + "", + "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", + "", + "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "GET", "header": [ { "key": "Content-Type", @@ -7139,39 +6943,45 @@ "type": "text" } ], - "body": { - "mode": "raw", - "raw": "\n{\n\"sourceMicroserviceUuid\": \"{{ms-id}}\",\n \"destMicroserviceUuid\": \"{{ms-id}}\",\n \"name\": \"my-route\"\n}" - }, "url": { - "raw": "{{host}}/api/v3/routes", + "raw": "{{host}}/api/v3/microservices?application={{application-name}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "routes" + "microservices" + ], + "query": [ + { + "key": "application", + "value": "{{application-name}}" + } ] } }, "response": [] }, { - "name": "Delete a Route", + "name": "Get Microservice By Id", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" ], "type": "text/javascript" } } ], "request": { - "method": "DELETE", + "method": "GET", "header": [ { "key": "Content-Type", @@ -7183,41 +6993,40 @@ "type": "text" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{host}}/api/v3/routes/{{flow-name}}/{{route-id}}", + "raw": "{{host}}/api/v3/microservices/{{ms-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "routes", - "{{flow-name}}", - "{{route-id}}" + "microservices", + "{{ms-id}}" ] } }, "response": [] }, { - "name": "Add a Port Mapping to Microservice", + "name": "Get Microservice without catalog By Id", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;" + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "GET", "header": [ { "key": "Content-Type", @@ -7229,12 +7038,8 @@ "type": "text" } ], - "body": { - "mode": "raw", - "raw": "{\n \"internal\": 15,\n \"external\": 155,\n \"publicMode\": false\n}" - }, "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping", + "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", "host": [ "{{host}}" ], @@ -7242,4319 +7047,27 @@ "api", "v3", "microservices", - "{{ms-id}}", - "port-mapping" + "{{ms-no-catalog-id}}" ] } }, "response": [] }, { - "name": "Get Port Mappings", + "name": "Update Microservice without catalog item", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('ports');" + "tests[\"Status code is 204\"] = responseCode.code === 204;" ], "type": "text/javascript" } } ], "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Port Mapping By Provided Internal Port", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping/15", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping", - "15" - ] - } - }, - "response": [] - }, - { - "name": "Create volume mapping", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "", - "postman.setGlobalVariable(\"volume-id\", data.id);", - "" - ] - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": " {\n \"hostDestination\": \"/var/dest7\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping" - ] - } - }, - "response": [] - }, - { - "name": "List volume mappings", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('volumeMappings');" - ] - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Delete volume mapping", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ] - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping/{{volume-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping", - "{{volume-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Microservice", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ] - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"withCleanup\": false\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Flow", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/flow/{{flow-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "flow", - "{{flow-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Catalog Item By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete User", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Microservices collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Catalog", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New Catalog Item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"item-id\", data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Catalog Items", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('catalogItems');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/catalog/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Catalog Item By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.category && data.hasOwnProperty('configExample')", - "&& data.publisher && data.hasOwnProperty('diskRequired') && data.hasOwnProperty('ramRequired') && data.picture && data.hasOwnProperty('isPublic')", - "&& data.hasOwnProperty('registryId') && data.hasOwnProperty('images') && data.hasOwnProperty('inputType') && data.hasOwnProperty('outputType');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Catalog Item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Catalog Item By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Catalog collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Tunnel", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1,\n \"dockerPruningFrequency\": 35,\n \"availableDiskThreshold\": 95,\n \"logLevel\": \"INFO\"\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "Open SSH Tunnel To ioFog Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"action\": \"open\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/tunnel", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "tunnel" - ] - } - }, - "response": [] - }, - { - "name": "Get Node SSH Tunnel Info", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/tunnel", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "tunnel" - ] - } - }, - "response": [] - }, - { - "name": "Close SSH Tunnel To ioFog Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"action\": \"close\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/tunnel", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "tunnel" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Tunnel collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Microservices", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-name\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Second Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-name-2\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name-second\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "New Catalog Item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"item-id\", data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"ms-name\", data.name);", - "postman.setGlobalVariable(\"ms-id\", data.uuid);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name2\",\n \"config\": \"string\",\n \"catalogItemId\": {{item-id}},\n \"application\": \"{{application-name}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 1,\n \"external\": 1,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice without catalog in second application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"namesec\",\n \"config\": \"string\",\n \"images\": [{\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice without catalog", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"ms-no-catalog-name\", data.name);", - "postman.setGlobalVariable(\"ms-no-catalog-id\", data.uuid);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name-without-catalog\",\n \"config\": \"string\",\n \"images\": [{\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 1,\n \"application\": \"{{application-name}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 2,\n \"external\": 2,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.name;", - "", - "postman.setGlobalVariable(\"route-name\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"route-name\",\n \"from\": \"{{ms-no-catalog-name}}\",\n \"to\": \"{{ms-name}}\",\n \"application\": \"{{application-name}}\"\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/routes", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes" - ] - } - }, - "response": [] - }, - { - "name": "Update Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "", - "postman.setGlobalVariable(\"route-name\", \"route-name-updated\");" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"route-name-updated\",\n \"from\": \"{{ms-no-catalog-name}}\",\n \"to\": \"{{ms-name}}\",\n \"application\": \"{{application-name}}\"\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/routes/{{application-name}}/{{route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{application-name}}", - "{{route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/routes/{{application-name}}/{{route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{application-name}}", - "{{route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get All Microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains three microservices\"] = data.microservices.length === 3;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains two microservices\"] = data.microservices.length === 2;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", - "", - "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", - "", - "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices?application={{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ], - "query": [ - { - "key": "application", - "value": "{{application-name}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Microservice By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservice without catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice without catalog item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"images\": [{\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 2,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "", - "", - "postman.setGlobalVariable(\"ms-name\", \"name3\");" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name3\",\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Microservice without catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world-updated\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.registryId === 2 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice without catalog item to give it a catalog item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"catalogItemId\": 14,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Microservice with catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 0 && data.hasOwnProperty('catalogItemId') && data.catalogItemId === 14 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice without catalog item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"images\": [{\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 2,\n \"catalogItemId\": null,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Microservice again without catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world-updated\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.registryId === 2 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Create a Route From Microservice to Receiver", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.name", - "", - "postman.setGlobalVariable(\"route-id\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\"from\": \"{{ms-name}}\",\n \"to\": \"{{ms-name}}\",\n \"name\": \"my-route\",\n \"application\": \"{{application-name}}\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/routes", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/routes/{{application-name}}/{{route-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{application-name}}", - "{{route-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Add a Port Mapping to Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"internal\": 15,\n \"external\": 155,\n \"publicMode\": false\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Get Port Mappings", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('ports');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Port Mapping By Provided Internal Port", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping/15", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping", - "15" - ] - } - }, - "response": [] - }, - { - "name": "Create volume mapping", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "", - "postman.setGlobalVariable(\"volume-id\", data.id);", - "" - ] - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": " {\n \"hostDestination\": \"/var/dest7\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping" - ] - } - }, - "response": [] - }, - { - "name": "List volume mappings", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('volumeMappings');" - ] - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Delete volume mapping", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ] - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping/{{volume-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping", - "{{volume-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Microservice", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ] - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"withCleanup\": false\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Catalog Item By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete User", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Microservices collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Diagnostics", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-name\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "New Catalog Item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"item-id\", data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"ms-id\", data.uuid);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name2\",\n \"config\": \"string\",\n \"catalogItemId\": {{item-id}},\n \"application\": \"{{application-name}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 1,\n \"external\": 1,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Request to Create Image Snapshot", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/image-snapshot", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "image-snapshot" - ] - } - }, - "response": [] - }, - { - "name": "Download Image Snapshot", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 500\"] = responseCode.code === 500;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Error message is valid\"] = data.name === 'ValidationError' && data.message === 'Image snapshot is not available for this microservice.';" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/image-snapshot", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "image-snapshot" - ] - } - }, - "response": [] - }, - { - "name": "Enable/Disable Microservice Strace Option", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"enable\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/strace", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "strace" - ] - } - }, - "response": [] - }, - { - "name": "Post Strace Data to FTP Server", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 500\"] = responseCode.code === 500;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ftpHost\": \"string\",\n \"ftpPort\": 0,\n \"ftpUser\": \"string\",\n \"ftpPass\": \"string\",\n \"ftpDestDir\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/strace", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "strace" - ] - } - }, - "response": [] - }, - { - "name": "Get Strace Data For Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('data');", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/strace?format=string", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "strace" - ], - "query": [ - { - "key": "format", - "value": "string" - } - ] - }, - "description": "available formats:\n\t- string\n\t- file" - }, - "response": [] - }, - { - "name": "Delete a Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}?withCleanUp=true", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}" - ], - "query": [ - { - "key": "withCleanUp", - "value": "true" - } - ] - } - }, - "response": [] - }, - { - "name": "Delete Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Catalog Item By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete User", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Diagnostics collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "ioFog", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -11568,136 +7081,34 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" + "raw": "{\n \"images\": [{\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 2,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" }, "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "Provisioning Key", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.key;", - "", - "postman.setGlobalVariable(\"provisioning-key\", data.key);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/provisioning-key", + "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{node-id}}", - "provisioning-key" + "microservices", + "{{ms-no-catalog-id}}" ] } }, "response": [] }, { - "name": "List ioFog Nodes", + "name": "Update Microservice", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Status code is 204\"] = responseCode.code === 204;", "", - "var data = JSON.parse(responseBody);", "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('fogs') && data.fogs.length === 1;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog-list?filters[0][key]=uuid&filters[0][value]={{node-id}}&filters[0][condition]=equals", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog-list" - ], - "query": [ - { - "key": "filters[0][key]", - "value": "uuid" - }, - { - "key": "filters[0][value]", - "value": "{{node-id}}" - }, - { - "key": "filters[0][condition]", - "value": "equals" - } - ] - } - }, - "response": [] - }, - { - "name": "Update Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" + "postman.setGlobalVariable(\"ms-name\", \"name3\");" ], "type": "text/javascript" } @@ -11718,25 +7129,25 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"isSystem\": false,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n}" + "raw": "{\n \"name\": \"name3\",\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" }, "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", + "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{node-id}}" + "microservices", + "{{ms-no-catalog-id}}" ] } }, "response": [] }, { - "name": "List system fogs", + "name": "Get Updated Microservice without catalog By Id", "event": [ { "listen": "test", @@ -11746,7 +7157,7 @@ "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('fogs') && data.fogs.length === 1;" + "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world-updated\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.registryId === 2 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" ], "type": "text/javascript" } @@ -11761,63 +7172,40 @@ }, { "key": "Authorization", - "value": "{{user-token}}" + "value": "{{user-token}}", + "type": "text" } ], "url": { - "raw": "{{host}}/api/v3/iofog-list?system=true", + "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog-list" - ], - "query": [ - { - "key": "system", - "value": "true" - } + "microservices", + "{{ms-no-catalog-id}}" ] } }, "response": [] }, { - "name": "Get Node By Id", + "name": "Update Microservice without catalog item to give it a catalog item", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.name && data.location && data.hasOwnProperty('gpsMode') && data.hasOwnProperty('latitude')", - "&& data.hasOwnProperty('longitude') && data.description && data.hasOwnProperty('lastActive') && data.daemonStatus && data.hasOwnProperty('daemonOperatingDuration') ", - "&& data.hasOwnProperty('daemonLastStart') && data.hasOwnProperty('memoryUsage') && data.hasOwnProperty('diskUsage') && data.hasOwnProperty('cpuUsage') ", - "&& data.hasOwnProperty('memoryViolation') && data.hasOwnProperty('diskViolation') && data.hasOwnProperty('cpuViolation') && data.hasOwnProperty('catalogItemStatus')", - "&& data.hasOwnProperty('repositoryCount') && data.hasOwnProperty('repositoryStatus') && data.hasOwnProperty('systemTime') && data.hasOwnProperty('lastStatusTime')", - "&& data.hasOwnProperty('ipAddress') && data.hasOwnProperty('processedMessages') && data.hasOwnProperty('catalogItemMessageCounts') && data.hasOwnProperty('messageSpeed')", - "&& data.hasOwnProperty('lastCommandTime') && data.hasOwnProperty('networkInterface') && data.hasOwnProperty('dockerUrl') && data.hasOwnProperty('diskLimit')", - "&& data.hasOwnProperty('diskDirectory') && data.hasOwnProperty('memoryLimit') && data.hasOwnProperty('cpuLimit') && data.hasOwnProperty('logLimit')", - "&& data.logDirectory && data.hasOwnProperty('bluetoothEnabled') && data.hasOwnProperty('abstractedHardwareEnabled') && data.hasOwnProperty('logFileCount') ", - "&& data.hasOwnProperty('version') && data.hasOwnProperty('isReadyToUpgrade') && data.hasOwnProperty('isReadyToRollback') && data.hasOwnProperty('statusFrequency')", - "&& data.hasOwnProperty('changeFrequency') && data.hasOwnProperty('deviceScanFrequency') && data.hasOwnProperty('tunnel') && data.hasOwnProperty('watchdogEnabled')", - "&& data.hasOwnProperty('fogTypeId') && data.hasOwnProperty('userId')", - "&& data.hasOwnProperty('logLevel') && data.hasOwnProperty('dockerPruningFrequency')", - "&& data.hasOwnProperty('availableDiskThreshold')", - "&& data.hasOwnProperty('timeZone')", - "&& data.hasOwnProperty('fogTypeId') && data.hasOwnProperty('userId') && data.hasOwnProperty('isSystem');" + "tests[\"Status code is 204\"] = responseCode.code === 204;" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -11829,40 +7217,44 @@ "type": "text" } ], + "body": { + "mode": "raw", + "raw": "{\n \"catalogItemId\": 14,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" + }, "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", + "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{node-id}}" + "microservices", + "{{ms-no-catalog-id}}" ] } }, "response": [] }, { - "name": "Node Version Command rollback", + "name": "Get Updated Microservice with catalog By Id", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 400;", + "tests[\"Status code is 200\"] = responseCode.code === 200;", "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response error message is valid\"] = data.name === 'ValidationError' && data.message === 'Can\\'t rollback version now. There are no backups on agent';" + "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 0 && data.hasOwnProperty('catalogItemId') && data.catalogItemId === 14 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "GET", "header": [ { "key": "Content-Type", @@ -11874,30 +7266,23 @@ "type": "text" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/version/rollback", + "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{node-id}}", - "version", - "rollback" + "microservices", + "{{ms-no-catalog-id}}" ] - }, - "description": "change version command\nAvailable values : upgrade, rollback" + } }, "response": [] }, { - "name": "Node Version Command upgrade", + "name": "Update Microservice without catalog item", "event": [ { "listen": "test", @@ -11910,7 +7295,7 @@ } ], "request": { - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -11918,47 +7303,48 @@ }, { "key": "Authorization", - "type": "text", - "value": "{{user-token}}" + "value": "{{user-token}}", + "type": "text" } ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n \"images\": [{\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 2,\n \"catalogItemId\": null,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" }, "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/version/upgrade", + "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{node-id}}", - "version", - "upgrade" + "microservices", + "{{ms-no-catalog-id}}" ] - }, - "description": "change version command\nAvailable values : upgrade, rollback" + } }, "response": [] }, { - "name": "Reboot Node", + "name": "Get Updated Microservice again without catalog By Id", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world-updated\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.registryId === 2 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "GET", "header": [ { "key": "Content-Type", @@ -11970,34 +7356,35 @@ "type": "text" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/reboot", + "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{node-id}}", - "reboot" + "microservices", + "{{ms-no-catalog-id}}" ] } }, "response": [] }, { - "name": "Prune Node", + "name": "Create a Route From Microservice to Receiver", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.name", + "", + "postman.setGlobalVariable(\"route-id\", data.name);" ], "type": "text/javascript" } @@ -12018,39 +7405,37 @@ ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\"from\": \"{{ms-name}}\",\n \"to\": \"{{ms-name}}\",\n \"name\": \"my-route\",\n \"application\": \"{{application-name}}\"\n}" }, "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/prune", + "raw": "{{host}}/api/v3/routes", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{node-id}}", - "prune" + "routes" ] } }, "response": [] }, { - "name": "Retrieves HAL Hardware Info", + "name": "Delete a Route", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;" + "tests[\"Status code is 204\"] = responseCode.code === 204;" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "DELETE", "header": [ { "key": "Content-Type", @@ -12062,38 +7447,41 @@ "type": "text" } ], + "body": { + "mode": "raw", + "raw": "" + }, "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/hal/hw", + "raw": "{{host}}/api/v3/routes/{{application-name}}/{{route-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{node-id}}", - "hal", - "hw" + "routes", + "{{application-name}}", + "{{route-id}}" ] } }, "response": [] }, { - "name": "Retrieves HAL USB Info", + "name": "Add a Port Mapping to Microservice", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;" + "tests[\"Status code is 201\"] = responseCode.code === 201;" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Content-Type", @@ -12105,39 +7493,45 @@ "type": "text" } ], + "body": { + "mode": "raw", + "raw": "{\n \"internal\": 15,\n \"external\": 155,\n \"publicMode\": false\n}" + }, "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/hal/usb", + "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{node-id}}", - "hal", - "usb" + "microservices", + "{{ms-id}}", + "port-mapping" ] } }, "response": [] }, { - "name": "Delete system node", + "name": "Get Port Mappings", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.hasOwnProperty('ports');" ], "type": "text/javascript" } } ], "request": { - "method": "DELETE", + "method": "GET", "header": [ { "key": "Content-Type", @@ -12145,37 +7539,34 @@ }, { "key": "Authorization", - "type": "text", - "value": "{{user-token}}" + "value": "{{user-token}}", + "type": "text" } ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", + "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{system-node-id}}" + "microservices", + "{{ms-id}}", + "port-mapping" ] } }, "response": [] }, { - "name": "Delete Node", + "name": "Delete a Port Mapping By Provided Internal Port", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;" + "tests[\"Status code is 204\"] = responseCode.code === 204;" ], "type": "text/javascript" } @@ -12199,39 +7590,45 @@ "raw": "" }, "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", + "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping/15", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{node-id}}" + "microservices", + "{{ms-id}}", + "port-mapping", + "15" ] } }, "response": [] }, { - "name": "Check Node Deleted", + "name": "Create volume mapping", "event": [ { "listen": "test", "script": { + "type": "text/javascript", "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Status code is 201\"] = responseCode.code === 201;", "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response does not contain any node\"] = data.hasOwnProperty('fogs') && data.fogs.length === 0;" - ], - "type": "text/javascript" + "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", + "", + "", + "postman.setGlobalVariable(\"volume-id\", data.id);", + "" + ] } } ], "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Content-Type", @@ -12242,124 +7639,87 @@ "value": "{{user-token}}" } ], + "body": { + "mode": "raw", + "raw": " {\n \"hostDestination\": \"/var/dest7\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }" + }, "url": { - "raw": "{{host}}/api/v3/iofog-list?filters[0][key]=uuid&filters[0][value]={{node-id}}&filters[0][condition]=equals", + "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping", "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog-list" - ], - "query": [ - { - "key": "filters[0][key]", - "value": "uuid" - }, - { - "key": "filters[0][value]", - "value": "{{node-id}}" - }, - { - "key": "filters[0][condition]", - "value": "equals" - } + "{{host}}" + ], + "path": [ + "api", + "v3", + "microservices", + "{{ms-id}}", + "volume-mapping" ] } }, "response": [] }, { - "name": "Delete User", + "name": "List volume mappings", "event": [ { "listen": "test", "script": { + "type": "text/javascript", "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.hasOwnProperty('volumeMappings');" + ] } } ], "request": { - "method": "DELETE", + "method": "GET", "header": [ { "key": "Authorization", "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{host}}/api/v3/user/profile", + "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping", "host": [ "{{host}}" ], "path": [ "api", "v3", - "user", - "profile" + "microservices", + "{{ms-id}}", + "volume-mapping" ] } }, "response": [] - } - ], - "description": "ioFog collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } }, { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Registries", - "item": [ - { - "name": "Create user", + "name": "Delete volume mapping", "event": [ { "listen": "test", "script": { + "type": "text/javascript", "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" + "tests[\"Status code is 204\"] = responseCode.code === 204;" + ] } } ], "request": { - "method": "POST", + "method": "DELETE", "header": [ + { + "key": "Authorization", + "value": "{{user-token}}" + }, { "key": "Content-Type", "value": "application/json" @@ -12367,93 +7727,85 @@ ], "body": { "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" + "raw": "" }, "url": { - "raw": "{{host}}/api/v3/user/signup", + "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping/{{volume-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "user", - "signup" + "microservices", + "{{ms-id}}", + "volume-mapping", + "{{volume-id}}" ] } }, "response": [] }, { - "name": "Login", + "name": "Delete a Microservice", "event": [ { "listen": "test", "script": { + "type": "text/javascript", "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", + "tests[\"Status code is 204\"] = responseCode.code === 204;", "" - ], - "type": "text/javascript" + ] } } ], "request": { - "method": "POST", + "method": "DELETE", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "{{user-token}}" } ], "body": { "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" + "raw": "{\n\t\"withCleanup\": false\n}" }, "url": { - "raw": "{{host}}/api/v3/user/login", + "raw": "{{host}}/api/v3/microservices/{{ms-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "user", - "login" + "microservices", + "{{ms-id}}" ] } }, "response": [] }, { - "name": "Create Registry", + "name": "Delete Application", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"reg-id\", data.id);", - "" + "tests[\"Status code is 204\"] = responseCode.code === 204;" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "DELETE", "header": [ { "key": "Content-Type", @@ -12467,37 +7819,39 @@ ], "body": { "mode": "raw", - "raw": "{\n \"url\": \"string\",\n \"isPublic\": true,\n \"username\": \"string\",\n \"password\": \"string\",\n \"email\": \"test@gmail.com\",\n \"requiresCert\": false,\n \"certificate\": \"string\"\n}" + "raw": "" }, "url": { - "raw": "{{host}}/api/v3/registries", + "raw": "{{host}}/api/v3/application/{{application-name}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "registries" + "application", + "{{application-name}}" ] } }, "response": [] }, { - "name": "Update Registry", + "name": "Delete Catalog Item By Id", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" + "tests[\"Status code is 204\"] = responseCode.code === 204;", + "" ], "type": "text/javascript" } } ], "request": { - "method": "PATCH", + "method": "DELETE", "header": [ { "key": "Content-Type", @@ -12505,48 +7859,46 @@ }, { "key": "Authorization", - "type": "text", - "value": "{{user-token}}" + "value": "{{user-token}}", + "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n \"url\": \"string2\",\n \"isPublic\": true,\n \"username\": \"string3\",\n \"password\": \"string4\",\n \"email\": \"test2@gmail.com\",\n \"requiresCert\": true,\n \"certificate\": \"string6\"\n}" + "raw": "" }, "url": { - "raw": "{{host}}/api/v3/registries/{{reg-id}}", + "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "registries", - "{{reg-id}}" + "catalog", + "microservices", + "{{item-id}}" ] } }, "response": [] }, { - "name": "Get Registries", + "name": "Delete system node", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('registries');" + "tests[\"Status code is 202\"] = responseCode.code === 202;", + "" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "DELETE", "header": [ { "key": "Content-Type", @@ -12554,32 +7906,38 @@ }, { "key": "Authorization", - "value": "{{user-token}}", - "type": "text" + "type": "text", + "value": "{{user-token}}" } ], + "body": { + "mode": "raw", + "raw": "" + }, "url": { - "raw": "{{host}}/api/v3/registries", + "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "registries" + "iofog", + "{{system-node-id}}" ] } }, "response": [] }, { - "name": "Delete a Registry", + "name": "Delete Node", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" + "tests[\"Status code is 202\"] = responseCode.code === 202;", + "" ], "type": "text/javascript" } @@ -12594,31 +7952,31 @@ }, { "key": "Authorization", - "value": "{{user-token}}", - "type": "text" + "type": "text", + "value": "{{user-token}}" } ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" }, "url": { - "raw": "{{host}}/api/v3/registries/{{reg-id}}", + "raw": "{{host}}/api/v3/iofog/{{node-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "registries", - "{{reg-id}}" + "iofog", + "{{node-id}}" ] } }, "response": [] }, { - "name": "Delete user", + "name": "Delete User", "event": [ { "listen": "test", @@ -12662,7 +8020,7 @@ "response": [] } ], - "description": "Registries collection", + "description": "Microservices collection", "event": [ { "listen": "prerequest", @@ -12685,7 +8043,7 @@ ] }, { - "name": "Edge Resources", + "name": "ioFog", "item": [ { "name": "Create user", @@ -12814,7 +8172,58 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" + "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" + }, + "url": { + "raw": "{{host}}/api/v3/iofog", + "host": [ + "{{host}}" + ], + "path": [ + "api", + "v3", + "iofog" + ] + } + }, + "response": [] + }, + { + "name": "New Node", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "tests[\"Status code is 201\"] = responseCode.code === 201;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.uuid;", + "", + "postman.setGlobalVariable(\"node-id\", data.uuid);", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "{{user-token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" }, "url": { "raw": "{{host}}/api/v3/iofog", @@ -12831,7 +8240,7 @@ "response": [] }, { - "name": "New Node", + "name": "Provisioning Key", "event": [ { "listen": "test", @@ -12841,10 +8250,9 @@ "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response validation passed\"] = data.uuid;", + "tests[\"Response validation passed\"] = data.key;", "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "postman.setGlobalVariable(\"node-name\", data.name);", + "postman.setGlobalVariable(\"provisioning-key\", data.key);", "" ], "type": "text/javascript" @@ -12852,7 +8260,7 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ { "key": "Content-Type", @@ -12864,39 +8272,34 @@ "type": "text" } ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, "url": { - "raw": "{{host}}/api/v3/iofog", + "raw": "{{host}}/api/v3/iofog/{{node-id}}/provisioning-key", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog" + "iofog", + "{{node-id}}", + "provisioning-key" ] } }, "response": [] }, { - "name": "Provisioning Key", + "name": "List ioFog Nodes", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", + "tests[\"Status code is 200\"] = responseCode.code === 200;", "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response validation passed\"] = data.key;", - "", - "postman.setGlobalVariable(\"provisioning-key\", data.key);", - "" + "tests[\"Response validation passed\"] = data.hasOwnProperty('fogs') && data.fogs.length === 1;" ], "type": "text/javascript" } @@ -12911,74 +8314,84 @@ }, { "key": "Authorization", - "type": "text", "value": "{{user-token}}" } ], "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/provisioning-key", + "raw": "{{host}}/api/v3/iofog-list?filters[0][key]=uuid&filters[0][value]={{node-id}}&filters[0][condition]=equals", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{node-id}}", - "provisioning-key" + "iofog-list" + ], + "query": [ + { + "key": "filters[0][key]", + "value": "uuid" + }, + { + "key": "filters[0][value]", + "value": "{{node-id}}" + }, + { + "key": "filters[0][condition]", + "value": "equals" + } ] } }, "response": [] }, { - "name": "Agent provision", + "name": "Update Node", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.token;", - "", - "postman.setGlobalVariable(\"agent-token\", data.token);" + "tests[\"Status code is 204\"] = responseCode.code === 204;" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "{{user-token}}", + "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"{{provisioning-key}}\"\n}" + "raw": "{\n \"name\": \"string\",\n \"isSystem\": false,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n}" }, "url": { - "raw": "{{host}}/api/v3/agent/provision", + "raw": "{{host}}/api/v3/iofog/{{node-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "agent", - "provision" + "iofog", + "{{node-id}}" ] } }, "response": [] }, { - "name": "Create Edge Resource", + "name": "List system fogs", "event": [ { "listen": "test", @@ -12988,20 +8401,14 @@ "", "var data = JSON.parse(responseBody);", "", - "tests[\"Response validation passed\"] = !!data.id;", - "", - "if (responseCode.code === 200) {", - " postman.setGlobalVariable(\"edge-resource-name\", data.name);", - " postman.setGlobalVariable(\"edge-resource-version\", data.version);", - "}", - "" + "tests[\"Response validation passed\"] = data.hasOwnProperty('fogs') && data.fogs.length === 1;" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "GET", "header": [ { "key": "Content-Type", @@ -13009,44 +8416,63 @@ }, { "key": "Authorization", - "value": "{{user-token}}", - "type": "text" + "value": "{{user-token}}" } ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"com.orange.smart-door\",\n \"version\": \"0.0.1\",\n \"description\": \"Orange Smart Door\",\n \"display\": {\n \"name\": \"Smart Door\",\n \"icon\": \"help\",\n \"color\": \"#ff0000\"\n },\n \"interfaceProtocol\": \"https\",\n \"interface\": {\n \"endpoints\": [\n {\n \"name\": \"liveness\",\n \"url\": \"https://localhost:91121\",\n \"method\": \"GET\"\n }\n ]\n },\n \"orchestrationTags\": [\n \"orange\",\n \"smart-door\"\n ]\n}" - }, "url": { - "raw": "{{host}}/api/v3/edgeResource", + "raw": "{{host}}/api/v3/iofog-list?system=true", "host": [ "{{host}}" ], "path": [ "api", "v3", - "edgeResource" + "iofog-list" + ], + "query": [ + { + "key": "system", + "value": "true" + } ] } }, "response": [] }, { - "name": "Update Edge Resource (different version)", + "name": "Get Node By Id", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 400\"] = responseCode.code === 400;", - "" + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.uuid && data.name && data.location && data.hasOwnProperty('gpsMode') && data.hasOwnProperty('latitude')", + "&& data.hasOwnProperty('longitude') && data.description && data.hasOwnProperty('lastActive') && data.daemonStatus && data.hasOwnProperty('daemonOperatingDuration') ", + "&& data.hasOwnProperty('daemonLastStart') && data.hasOwnProperty('memoryUsage') && data.hasOwnProperty('diskUsage') && data.hasOwnProperty('cpuUsage') ", + "&& data.hasOwnProperty('memoryViolation') && data.hasOwnProperty('diskViolation') && data.hasOwnProperty('cpuViolation') && data.hasOwnProperty('catalogItemStatus')", + "&& data.hasOwnProperty('repositoryCount') && data.hasOwnProperty('repositoryStatus') && data.hasOwnProperty('systemTime') && data.hasOwnProperty('lastStatusTime')", + "&& data.hasOwnProperty('ipAddress') && data.hasOwnProperty('processedMessages') && data.hasOwnProperty('catalogItemMessageCounts') && data.hasOwnProperty('messageSpeed')", + "&& data.hasOwnProperty('lastCommandTime') && data.hasOwnProperty('networkInterface') && data.hasOwnProperty('dockerUrl') && data.hasOwnProperty('diskLimit')", + "&& data.hasOwnProperty('diskDirectory') && data.hasOwnProperty('memoryLimit') && data.hasOwnProperty('cpuLimit') && data.hasOwnProperty('logLimit')", + "&& data.logDirectory && data.hasOwnProperty('bluetoothEnabled') && data.hasOwnProperty('abstractedHardwareEnabled') && data.hasOwnProperty('logFileCount') ", + "&& data.hasOwnProperty('version') && data.hasOwnProperty('isReadyToUpgrade') && data.hasOwnProperty('isReadyToRollback') && data.hasOwnProperty('statusFrequency')", + "&& data.hasOwnProperty('changeFrequency') && data.hasOwnProperty('deviceScanFrequency') && data.hasOwnProperty('tunnel') && data.hasOwnProperty('watchdogEnabled')", + "&& data.hasOwnProperty('fogTypeId') && data.hasOwnProperty('userId')", + "&& data.hasOwnProperty('logLevel') && data.hasOwnProperty('dockerPruningFrequency')", + "&& data.hasOwnProperty('availableDiskThreshold')", + "&& data.hasOwnProperty('timeZone')", + "&& data.hasOwnProperty('fogTypeId') && data.hasOwnProperty('userId') && data.hasOwnProperty('isSystem');" ], "type": "text/javascript" } } ], "request": { - "method": "PUT", + "method": "GET", "header": [ { "key": "Content-Type", @@ -13054,49 +8480,44 @@ }, { "key": "Authorization", - "type": "text", - "value": "{{user-token}}" + "value": "{{user-token}}", + "type": "text" } ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"com.orange.smart-door\",\n \"version\": \"0.0.2\",\n \"description\": \"Orange Smart Door\",\n \"display\": {\n \"name\": \"Smart Door\",\n \"icon\": \"help\",\n \"color\": \"#ff0000\"\n },\n \"interfaceProtocol\": \"https\",\n \"interface\": {\n \"endpoints\": [\n {\n \"name\": \"liveness\",\n \"url\": \"https://localhost:91121\",\n \"method\": \"GET\"\n }\n ]\n }\n}" - }, "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}", + "raw": "{{host}}/api/v3/iofog/{{node-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}" + "iofog", + "{{node-id}}" ] } }, "response": [] }, { - "name": "Update Edge Resource", + "name": "Node Version Command rollback", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Status code is 204\"] = responseCode.code === 400;", "", - "if (tests[\"Status code is 200\"]) {", - " postman.setGlobalVariable(\"edge-resource-name\", \"com.orange.smart-door2\");", - "}" + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response error message is valid\"] = data.name === 'ValidationError' && data.message === 'Can\\'t rollback version now. There are no backups on agent';" ], "type": "text/javascript" } } ], "request": { - "method": "PUT", + "method": "POST", "header": [ { "key": "Content-Type", @@ -13104,50 +8525,47 @@ }, { "key": "Authorization", - "type": "text", - "value": "{{user-token}}" + "value": "{{user-token}}", + "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"com.orange.smart-door2\",\n \"version\": \"0.0.1\",\n \"description\": \"Orange Smart Door\",\n \"display\": {\n \"name\": \"Smart Door\",\n \"icon\": \"help\",\n \"color\": \"#ff0000\"\n },\n \"interfaceProtocol\": \"https\",\n \"interface\": {\n \"endpoints\": [\n {\n \"name\": \"liveness\",\n \"url\": \"https://localhost:91121\",\n \"method\": \"GET\"\n },\n {\n \"name\": \"version\",\n \"url\": \"https://localhost:91121/version\",\n \"method\": \"GET\"\n }\n ]\n },\n \"orchestrationTags\": [\n \"orange\",\n \"smart-door\",\n \"smart-door-v0.0.1\"\n ]\n}" + "raw": "" }, "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}", + "raw": "{{host}}/api/v3/iofog/{{node-id}}/version/rollback", "host": [ "{{host}}" ], "path": [ "api", "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}" + "iofog", + "{{node-id}}", + "version", + "rollback" ] - } + }, + "description": "change version command\nAvailable values : upgrade, rollback" }, "response": [] }, { - "name": "Rename Edge Resource", + "name": "Node Version Command upgrade", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - " postman.setGlobalVariable(\"edge-resource-name\", \"com.orange.smart-door\");", - "", - "if (tests[\"Status code is 200\"]) {", - " postman.setGlobalVariable(\"edge-resource-name\", \"com.orange.smart-door\");", - "}" + "tests[\"Status code is 204\"] = responseCode.code === 204;" ], "type": "text/javascript" } } ], "request": { - "method": "PUT", + "method": "POST", "header": [ { "key": "Content-Type", @@ -13161,78 +8579,80 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"com.orange.smart-door\"\n}" + "raw": "" }, "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}", + "raw": "{{host}}/api/v3/iofog/{{node-id}}/version/upgrade", "host": [ "{{host}}" ], "path": [ "api", "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}" + "iofog", + "{{node-id}}", + "version", + "upgrade" ] - } + }, + "description": "change version command\nAvailable values : upgrade, rollback" }, "response": [] }, { - "name": "Get Edge Resource", + "name": "Reboot Node", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Contains metadata\"] = data.name && data.display && data.display.color && data.display.icon && data.display.name && data.interfaceProtocol && data.interfaceProtocol;", - "", - "tests[\"Has interface details\"] = data.interface && data.interface.endpoints && data.interface.endpoints.length === 2", - "tests[\"Has orchestration tags\"] = data.orchestrationTags && data.orchestrationTags.length === 3" + "tests[\"Status code is 204\"] = responseCode.code === 204;" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Authorization", "value": "{{user-token}}", "type": "text" } ], + "body": { + "mode": "raw", + "raw": "" + }, "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}", + "raw": "{{host}}/api/v3/iofog/{{node-id}}/reboot", "host": [ "{{host}}" ], "path": [ "api", "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}" + "iofog", + "{{node-id}}", + "reboot" ] } }, "response": [] }, { - "name": "Link Edge Resource", + "name": "Prune Node", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "" + "tests[\"Status code is 204\"] = responseCode.code === 204;" ], "type": "text/javascript" } @@ -13247,43 +8667,38 @@ }, { "key": "Authorization", - "type": "text", - "value": "{{user-token}}" + "value": "{{user-token}}", + "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n \"uuid\": \"{{node-id}}\"\n}" + "raw": "" }, "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}/link", + "raw": "{{host}}/api/v3/iofog/{{node-id}}/prune", "host": [ "{{host}}" ], "path": [ "api", "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}", - "link" + "iofog", + "{{node-id}}", + "prune" ] } }, "response": [] }, { - "name": "Get agent config changes", + "name": "Retrieves HAL Hardware Info", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('linkedEdgeResources') && data.linkedEdgeResources === true" + "tests[\"Status code is 200\"] = responseCode.code === 200;" ], "type": "text/javascript" } @@ -13298,45 +8713,35 @@ }, { "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" + "value": "{{user-token}}", + "type": "text" } ], "url": { - "raw": "{{host}}/api/v3/agent/config/changes", + "raw": "{{host}}/api/v3/iofog/{{node-id}}/hal/hw", "host": [ "{{host}}" ], "path": [ "api", "v3", - "agent", - "config", - "changes" + "iofog", + "{{node-id}}", + "hal", + "hw" ] } }, "response": [] }, { - "name": "Get agent linked Edge resources", + "name": "Retrieves HAL USB Info", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Contains edgeResources\"] = data.edgeResources && data.edgeResources.length === 1", - "", - "if (tests[\"Contains edgeResources\"]) {", - " var edgeResource = data.edgeResources[0] ", - "", - " tests[\"Has display information\"] = edgeResource.display && edgeResource.display.name", - " tests[\"Has interface\"] = edgeResource.interface && edgeResource.interface.endpoints.length > 0", - "}" + "tests[\"Status code is 200\"] = responseCode.code === 200;" ], "type": "text/javascript" } @@ -13351,44 +8756,35 @@ }, { "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" + "value": "{{user-token}}", + "type": "text" } ], "url": { - "raw": "{{host}}/api/v3/agent/edgeResources", + "raw": "{{host}}/api/v3/iofog/{{node-id}}/hal/usb", "host": [ "{{host}}" ], "path": [ "api", "v3", - "agent", - "edgeResources" + "iofog", + "{{node-id}}", + "hal", + "usb" ] } }, "response": [] }, { - "name": "Get Edge resource associated to Agent", + "name": "Delete system node", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Contains edgeResources\"] = data.edgeResources && data.edgeResources.length === 1", - "", - "if (tests[\"Contains edgeResources\"]) {", - " var edgeResource = data.edgeResources[0] ", - "", - " tests[\"Has display information\"] = edgeResource.display && edgeResource.display.name", - " tests[\"Has tags\"] = data.tags && data.tags.length === 3", - "}", + "tests[\"Status code is 202\"] = responseCode.code === 202;", "" ], "type": "text/javascript" @@ -13396,16 +8792,24 @@ } ], "request": { - "method": "GET", + "method": "DELETE", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Authorization", "type": "text", "value": "{{user-token}}" } ], + "body": { + "mode": "raw", + "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" + }, "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", + "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", "host": [ "{{host}}" ], @@ -13413,21 +8817,20 @@ "api", "v3", "iofog", - "{{node-id}}" + "{{system-node-id}}" ] } }, "response": [] }, { - "name": "Unlink Edge Resource", + "name": "Delete Node", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "" + "tests[\"Status code is 202\"] = responseCode.code === 202;" ], "type": "text/javascript" } @@ -13442,33 +8845,31 @@ }, { "key": "Authorization", - "type": "text", - "value": "{{user-token}}" + "value": "{{user-token}}", + "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n \"uuid\": \"{{node-id}}\"\n}" + "raw": "" }, "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}/link", + "raw": "{{host}}/api/v3/iofog/{{node-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}", - "link" + "iofog", + "{{node-id}}" ] } }, "response": [] }, { - "name": "Get Edge resource associated to Agent", + "name": "Check Node Deleted", "event": [ { "listen": "test", @@ -13478,9 +8879,7 @@ "", "var data = JSON.parse(responseBody);", "", - "tests[\"Contains edgeResources\"] = data.edgeResources && data.edgeResources.length === 0", - "", - "" + "tests[\"Response does not contain any node\"] = data.hasOwnProperty('fogs') && data.fogs.length === 0;" ], "type": "text/javascript" } @@ -13489,123 +8888,159 @@ "request": { "method": "GET", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Authorization", - "type": "text", "value": "{{user-token}}" } ], "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", + "raw": "{{host}}/api/v3/iofog-list?filters[0][key]=uuid&filters[0][value]={{node-id}}&filters[0][condition]=equals", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{node-id}}" + "iofog-list" + ], + "query": [ + { + "key": "filters[0][key]", + "value": "uuid" + }, + { + "key": "filters[0][value]", + "value": "{{node-id}}" + }, + { + "key": "filters[0][condition]", + "value": "equals" + } ] } }, "response": [] }, { - "name": "Add Edge Resource version", + "name": "Delete User", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = !!data.id;", - "", - "if (responseCode.code === 200) {", - " postman.setGlobalVariable(\"edge-resource-name\", data.name);", - " postman.setGlobalVariable(\"edge-resource-version-2\", data.version);", - "}", - "" + "tests[\"Status code is 204\"] = responseCode.code === 204;" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "DELETE", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", - "type": "text", "value": "{{user-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"com.orange.smart-door\",\n \"version\": \"0.0.2\",\n \"description\": \"Orange Smart Door\",\n \"display\": {\n \"name\": \"Smart Door\",\n \"icon\": \"help\",\n \"color\": \"#ff0000\"\n },\n \"interfaceProtocol\": \"https\",\n \"interface\": {\n \"endpoints\": [\n {\n \"name\": \"liveness\",\n \"url\": \"https://localhost:91121\",\n \"method\": \"GET\"\n }\n ]\n }\n}" + "raw": "" }, "url": { - "raw": "{{host}}/api/v3/edgeResource", + "raw": "{{host}}/api/v3/user/profile", "host": [ "{{host}}" ], "path": [ "api", "v3", - "edgeResource" + "user", + "profile" ] } }, "response": [] + } + ], + "description": "ioFog collection", + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } }, { - "name": "List Edge Resources", + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + }, + { + "name": "Registries", + "item": [ + { + "name": "Create user", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", + "tests[\"Status code is 201\"] = responseCode.code === 201;", "", "var data = JSON.parse(responseBody);", "", - "tests[\"Contains 2 resources\"] = data.edgeResources && data.edgeResources.length === 2" + "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "POST", "header": [ { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" + "key": "Content-Type", + "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" + }, "url": { - "raw": "{{host}}/api/v3/edgeResources", + "raw": "{{host}}/api/v3/user/signup", "host": [ "{{host}}" ], "path": [ "api", "v3", - "edgeResources" + "user", + "signup" ] } }, "response": [] }, { - "name": "List Edge Resources versions", + "name": "Login", "event": [ { "listen": "test", @@ -13615,57 +9050,57 @@ "", "var data = JSON.parse(responseBody);", "", - "tests[\"Contains 2 resources\"] = data.edgeResources && data.edgeResources.length === 2", - "", - "data = data.edgeResources[0]", + "tests[\"Response validation passed\"] = data.accessToken;", "", - "tests[\"Contains metadata\"] = data.name && data.display && data.display.color && data.display.icon && data.display.name && data.interfaceProtocol && data.interfaceProtocol;", "", - "tests[\"Has interface details\"] = data.interface && data.interface.endpoints && data.interface.endpoints.length === 2", - "tests[\"Has orchestration tags\"] = data.orchestrationTags && data.orchestrationTags.length === 3" + "postman.setGlobalVariable(\"user-token\", data.accessToken);", + "" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "method": "POST", "header": [ { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" + "key": "Content-Type", + "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" + }, "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}?=", + "raw": "{{host}}/api/v3/user/login", "host": [ "{{host}}" ], "path": [ "api", "v3", - "edgeResource", - "{{edge-resource-name}}" - ], - "query": [ - { - "key": "", - "value": "" - } + "user", + "login" ] } }, "response": [] }, { - "name": "Delete Edge Resource V2", + "name": "Create Registry", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", + "tests[\"Status code is 201\"] = responseCode.code === 201;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", + "", + "postman.setGlobalVariable(\"reg-id\", data.id);", "" ], "type": "text/javascript" @@ -13673,7 +9108,7 @@ } ], "request": { - "method": "DELETE", + "method": "POST", "header": [ { "key": "Content-Type", @@ -13681,46 +9116,43 @@ }, { "key": "Authorization", - "type": "text", - "value": "{{user-token}}" + "value": "{{user-token}}", + "type": "text" } ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n \"url\": \"string\",\n \"isPublic\": true,\n \"username\": \"string\",\n \"password\": \"string\",\n \"email\": \"test@gmail.com\",\n \"requiresCert\": false,\n \"certificate\": \"string\"\n}" }, "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version-2}}", + "raw": "{{host}}/api/v3/registries", "host": [ "{{host}}" ], "path": [ "api", "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version-2}}" + "registries" ] } }, "response": [] }, { - "name": "Delete Edge Resource", + "name": "Update Registry", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" + "tests[\"Status code is 204\"] = responseCode.code === 204;" ], "type": "text/javascript" } } ], "request": { - "method": "DELETE", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -13734,40 +9166,42 @@ ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n \"url\": \"string2\",\n \"isPublic\": true,\n \"username\": \"string3\",\n \"password\": \"string4\",\n \"email\": \"test2@gmail.com\",\n \"requiresCert\": true,\n \"certificate\": \"string6\"\n}" }, "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}", + "raw": "{{host}}/api/v3/registries/{{reg-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}" + "registries", + "{{reg-id}}" ] } }, "response": [] }, { - "name": "Delete system node", + "name": "Get Registries", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" + "tests[\"Status code is 200\"] = responseCode.code === 200;", + "", + "var data = JSON.parse(responseBody);", + "", + "tests[\"Response validation passed\"] = data.hasOwnProperty('registries');" ], "type": "text/javascript" } } ], "request": { - "method": "DELETE", + "method": "GET", "header": [ { "key": "Content-Type", @@ -13775,37 +9209,32 @@ }, { "key": "Authorization", - "type": "text", - "value": "{{user-token}}" + "value": "{{user-token}}", + "type": "text" } ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", + "raw": "{{host}}/api/v3/registries", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{system-node-id}}" + "registries" ] } }, "response": [] }, { - "name": "Delete Node", + "name": "Delete a Registry", "event": [ { "listen": "test", "script": { "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;" + "tests[\"Status code is 204\"] = responseCode.code === 204;" ], "type": "text/javascript" } @@ -13829,22 +9258,22 @@ "raw": "" }, "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", + "raw": "{{host}}/api/v3/registries/{{reg-id}}", "host": [ "{{host}}" ], "path": [ "api", "v3", - "iofog", - "{{node-id}}" + "registries", + "{{reg-id}}" ] } }, "response": [] }, { - "name": "Delete User", + "name": "Delete user", "event": [ { "listen": "test", @@ -13887,42 +9316,32 @@ }, "response": [] } + ], + "description": "Registries collection", + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } ] }, { "name": "Capabilities", "item": [ - { - "name": "Controller is EdgeResource capable", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "HEAD", - "header": [], - "url": { - "raw": "{{host}}/api/v3/capabilities/edgeResources", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "capabilities", - "edgeResources" - ] - } - }, - "response": [] - }, { "name": "Controller is Application Template capable", "event": [ @@ -13984,4 +9403,4 @@ "type": "string" } ] -} \ No newline at end of file +} diff --git a/test/src/controllers/agent-controller.test.js b/test/src/controllers/agent-controller.test.js index 549b6eec..17082bb3 100644 --- a/test/src/controllers/agent-controller.test.js +++ b/test/src/controllers/agent-controller.test.js @@ -500,87 +500,6 @@ describe('Agent Controller', () => { }) }) - describe('getAgentStraceEndPoint()', () => { - def('fog', () => 'fog!') - - def('req', () => ({ - body: {}, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.getAgentStraceEndPoint($req, $fog)) - - beforeEach(() => { - $sandbox.stub(AgentService, 'getAgentStrace').returns($response) - }) - - it('calls AgentService.getAgentStrace with correct args', async () => { - await $subject - expect(AgentService.getAgentStrace).to.have.been.calledWith($fog) - }) - - context('when AgentService#getAgentStrace fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AgentService#getAgentStrace succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('updateAgentStraceEndPoint()', () => { - def('fog', () => 'fog!') - def('microserviceUuid', () => 'microserviceUuid') - def('buffer', () => 'testBuffer') - - def('straceData', [{ - microserviceUuid: $microserviceUuid, - buffer: $buffer, - }]) - - def('req', () => ({ - body: { - straceData: $straceData, - }, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.updateAgentStraceEndPoint($req, $fog)) - - beforeEach(() => { - $sandbox.stub(AgentService, 'updateAgentStrace').returns($response) - }) - - it('calls AgentService.updateAgentStrace with correct args', async () => { - await $subject - expect(AgentService.updateAgentStrace).to.have.been.calledWith({ - straceData: $straceData, - }, $fog) - }) - - context('when AgentService#updateAgentStrace fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AgentService#updateAgentStrace succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - describe('getAgentChangeVersionCommandEndPoint()', () => { def('fog', () => 'fog!') @@ -732,74 +651,4 @@ describe('Agent Controller', () => { }) }) }) - - describe('getImageSnapshotEndPoint()', () => { - def('fog', () => 'fog!') - - def('req', () => ({ - body: {}, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.getImageSnapshotEndPoint($req, $fog)) - - beforeEach(() => { - $sandbox.stub(AgentService, 'getImageSnapshot').returns($response) - }) - - it('calls AgentService.getImageSnapshot with correct args', async () => { - await $subject - expect(AgentService.getImageSnapshot).to.have.been.calledWith($fog) - }) - - context('when AgentService#getImageSnapshot fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AgentService#getImageSnapshot succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('putImageSnapshotEndPoint()', () => { - def('fog', () => 'fog!') - - def('req', () => ({ - body: {}, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.putImageSnapshotEndPoint($req, $fog)) - - beforeEach(() => { - $sandbox.stub(AgentService, 'putImageSnapshot').returns($response) - }) - - it('calls AgentService.putImageSnapshot with correct args', async () => { - await $subject - expect(AgentService.putImageSnapshot).to.have.been.calledWith($req, $fog) - }) - - context('when AgentService#putImageSnapshot fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AgentService#putImageSnapshot succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) }) diff --git a/test/src/controllers/diagnostics-controller.test.js b/test/src/controllers/diagnostics-controller.test.js deleted file mode 100644 index f9038262..00000000 --- a/test/src/controllers/diagnostics-controller.test.js +++ /dev/null @@ -1,235 +0,0 @@ -const { expect } = require('chai') -const sinon = require('sinon') - -const DiagnosticController = require('../../../src/controllers/diagnostic-controller') -const DiagnosticService = require('../../../src/services/diagnostic-service') - -describe('Diagnostic Controller', () => { - def('subject', () => DiagnosticController) - def('sandbox', () => sinon.createSandbox()) - - afterEach(() => $sandbox.restore()) - - describe('.changeMicroserviceStraceStateEndPoint()', () => { - def('user', () => 'user!') - def('uuid', () => 'testUuid') - - def('enable', () => true) - - def('req', () => ({ - params: { - uuid: $uuid, - }, - body: { - enable: $enable, - }, - })) - - def('response', () => Promise.resolve()) - def('subject', () => $subject.changeMicroserviceStraceStateEndPoint($req, $user)) - - beforeEach(() => { - $sandbox.stub(DiagnosticService, 'changeMicroserviceStraceState').returns($response) - }) - - it('calls DiagnosticService.changeMicroserviceStraceState with correct args', async () => { - await $subject - expect(DiagnosticService.changeMicroserviceStraceState).to.have.been.calledWith($uuid, { - enable: $enable, - }, $user, false) - }) - - context('when DiagnosticService#changeMicroserviceStraceState fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when DiagnosticService#changeMicroserviceStraceState succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('.getMicroserviceStraceDataEndPoint()', () => { - def('user', () => 'user!') - def('uuid', () => 'testUuid') - def('format', () => 'string') - - def('req', () => ({ - params: { - uuid: $uuid, - }, - query: { - format: $format, - }, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.getMicroserviceStraceDataEndPoint($req, $user)) - - beforeEach(() => { - $sandbox.stub(DiagnosticService, 'getMicroserviceStraceData').returns($response) - }) - - it('calls DiagnosticService.getMicroserviceStraceData with correct args', async () => { - await $subject - expect(DiagnosticService.getMicroserviceStraceData).to.have.been.calledWith($uuid, { - format: $format, - }, $user, false) - }) - - context('when DiagnosticService#getMicroserviceStraceData fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when DiagnosticService#getMicroserviceStraceData succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('.postMicroserviceStraceDataToFtpEndPoint()', () => { - def('user', () => 'user!') - def('uuid', () => 'testUuid') - - def('ftpHost', () => 'testHost') - def('ftpPort', () => 15) - def('ftpPass', () => 'ftpPass') - def('ftpDestDir', () => 'ftpDestDirectory') - - def('req', () => ({ - params: { - uuid: $uuid, - }, - body: { - ftpHost: $ftpHost, - ftpPort: $ftpPort, - ftpPass: $ftpPass, - ftpDestDir: $ftpDestDir, - }, - })) - - def('response', () => Promise.resolve()) - def('subject', () => $subject.postMicroserviceStraceDataToFtpEndPoint($req, $user)) - - beforeEach(() => { - $sandbox.stub(DiagnosticService, 'postMicroserviceStraceDatatoFtp').returns($response) - }) - - it('calls DiagnosticService.postMicroserviceStraceDatatoFtp with correct args', async () => { - await $subject - expect(DiagnosticService.postMicroserviceStraceDatatoFtp).to.have.been.calledWith($uuid, { - ftpHost: $ftpHost, - ftpPort: $ftpPort, - ftpPass: $ftpPass, - ftpDestDir: $ftpDestDir, - }, $user, false) - }) - - context('when DiagnosticService#postMicroserviceStraceDatatoFtp fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when DiagnosticService#postMicroserviceStraceDatatoFtp succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('.createMicroserviceImageSnapshotEndPoint()', () => { - def('user', () => 'user!') - def('uuid', () => 'testUuid') - - def('req', () => ({ - params: { - uuid: $uuid, - }, - })) - - def('response', () => Promise.resolve()) - def('subject', () => $subject.createMicroserviceImageSnapshotEndPoint($req, $user)) - - beforeEach(() => { - $sandbox.stub(DiagnosticService, 'postMicroserviceImageSnapshotCreate').returns($response) - }) - - it('calls DiagnosticService.postMicroserviceImageSnapshotCreate with correct args', async () => { - await $subject - expect(DiagnosticService.postMicroserviceImageSnapshotCreate).to.have.been.calledWith($uuid, $user, false) - }) - - context('when DiagnosticService#postMicroserviceImageSnapshotCreate fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when DiagnosticService#postMicroserviceImageSnapshotCreate succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('.getMicroserviceImageSnapshotEndPoint()', () => { - def('user', () => 'user!') - def('uuid', () => 'testUuid') - - def('req', () => ({ - params: { - uuid: $uuid, - }, - })) - - def('response', () => Promise.resolve()) - def('subject', () => $subject.getMicroserviceImageSnapshotEndPoint($req, $user)) - - beforeEach(() => { - $sandbox.stub(DiagnosticService, 'getMicroserviceImageSnapshot').returns($response) - }) - - it('calls DiagnosticService.getMicroserviceImageSnapshot with correct args', async () => { - await $subject - expect(DiagnosticService.getMicroserviceImageSnapshot).to.have.been.calledWith($uuid, $user, false) - }) - - context('when DiagnosticService.getMicroserviceImageSnapshot fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when DiagnosticService.getMicroserviceImageSnapshot succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) -}) diff --git a/test/src/middlewares/rvaluesVarSubstitionMiddleware.test.js b/test/src/middlewares/rvaluesVarSubstitionMiddleware.test.js index de6e6d8f..b0263051 100755 --- a/test/src/middlewares/rvaluesVarSubstitionMiddleware.test.js +++ b/test/src/middlewares/rvaluesVarSubstitionMiddleware.test.js @@ -5,7 +5,6 @@ const { substitutionMiddleware } = require('../../../src/helpers/template-helper const MicroservicesService = require('../../../src/services/microservices-service') const ApplicationManager = require('../../../src/data/managers/application-manager') const FogService = require('../../../src/services/iofog-service') -const EdgeResourceService = require('../../../src/services/edge-resource-service') describe('rvaluesVarSubstitionMiddleware', () => { def('subject', () => substitutionMiddleware) @@ -77,9 +76,6 @@ describe('rvaluesVarSubstitionMiddleware', () => { microservices: [] })) def('responseFog', () => ({})) - def('responseEdgeRes', () => ({ - edgeResources: { name: 'testedgeres' } - })) def('response', () => Promise.resolve()) def('nextfct', () => sinon.spy()) @@ -89,7 +85,6 @@ describe('rvaluesVarSubstitionMiddleware', () => { $sandbox.stub(ApplicationManager, 'findOnePopulated').resolves($responseApp) $sandbox.stub(MicroservicesService, 'listMicroservicesEndPoint').resolves($responseApp) $sandbox.stub(FogService, 'getFogEndPoint').resolves($responseFog) - $sandbox.stub(EdgeResourceService, 'getEdgeResource').resolves($responseEdgeRes) }) it('calls next after POST body substitution', async () => { @@ -158,84 +153,10 @@ describe('rvaluesVarSubstitionMiddleware', () => { expect(ApplicationManager.findOnePopulated).to.have.been.calledWith({ exclude: ['created_at', 'updated_at'] }, { fakeTransaction: true }) expect(MicroservicesService.listMicroservicesEndPoint).to.have.been.called expect(MicroservicesService.listMicroservicesEndPoint).to.have.been.calledWith({ applicationName: $redisAppName }, false) - expect(EdgeResourceService.getEdgeResource).to.not.have.been.called expect($req.body.serviceredisURL).to.be.equal('myhost01:6379') expect($req.body.videoURL).to.be.equal('http://mycam/img/video.mjpeg') }) }) - - context('Variables substitution and filter edgeresource', () => { - def('responseApp', () => ({ - microservices: [ - { - name: 'objdetecv4', - applicationId: 1, - ports: [ - { - internal: 8080, - external: 8091, - publicMode: false - } - ], - env: [ - { - key: 'RES_URL', - value: 'http://mycam/img/video.mjpeg' - } - ] - }, - { - name: 'redis', - iofogUuid: 'TkLh8wzcxb86CRnHQyJkx6VF468JFd4f', - ports: [ - { - internal: 6379, - external: 6379, - publicMode: false - } - ], - application: 'main-app', - flowId: 1 - } - ] - })) - - context('edgeresource finding with version', () => { - def('body', () => ({ - body: { - name: $name, - description: '{{ self.name | upcase }}', - edgeRes: '{{ \"edgeRes\" | findEdgeResource: "0.1.0" | json }}' - }, - })) - it('performs variable substitutions and applies filter, looking edge resource with version', async () => { - await $subject - expect($nextfct).to.have.been.called - expect(EdgeResourceService.getEdgeResource).to.have.been.called - expect(EdgeResourceService.getEdgeResource).to.have.been.calledWith({ name: 'edgeRes', version: '0.1.0' }) - - expect($req.body.edgeRes).to.be.equal(JSON.stringify($responseEdgeRes)) - }) - }) - - context('edgeresource finding without version', () => { - def('body', () => ({ - body: { - name: $name, - description: '{{ self.name | upcase }}', - edgeResWithoutVersion: '{{ \"edgeRes\" | findEdgeResource | json }}' - }, - })) - it('performs variable substitutions and applies filter, looking edge resource without version', async () => { - await $subject - expect($nextfct).to.have.been.called - expect(EdgeResourceService.getEdgeResource).to.have.been.called - expect(EdgeResourceService.getEdgeResource).to.have.been.calledWith({ name: 'edgeRes', version: undefined }) - - expect($req.body.edgeResWithoutVersion).to.be.equal(JSON.stringify($responseEdgeRes)) - }) - }) - }) }) }) diff --git a/test/src/services/agent-service.test.js b/test/src/services/agent-service.test.js index 2b362c7c..5532cb23 100644 --- a/test/src/services/agent-service.test.js +++ b/test/src/services/agent-service.test.js @@ -15,7 +15,6 @@ const ApplicationManager = require('../../../src/data/managers/application-manag const MicroserviceService = require('../../../src/services/microservices-service') const RegistryManager = require('../../../src/data/managers/registry-manager') const TunnelManager = require('../../../src/data/managers/tunnel-manager') -const StraceManager = require('../../../src/data/managers/strace-manager') const ioFogVersionCommandManager = require('../../../src/data/managers/iofog-version-command-manager') const ioFogProvisionKeyManager = require('../../../src/data/managers/iofog-provision-key-manager') const HWInfoManager = require('../../../src/data/managers/hw-info-manager') @@ -516,9 +515,7 @@ describe('Agent Service', () => { routing: undefined, registries: undefined, tunnel: undefined, - diagnostics: undefined, routerChanged: undefined, - isImageSnapshot: undefined, prune: undefined, } @@ -1099,7 +1096,6 @@ describe('Agent Service', () => { logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, ports: 'testPorts', volumeMappings: 'testVolumeMappings', - imageSnapshot: 'testImageSnapshot', delete: false, deleteWithCleanup: false, catalogItem: { @@ -1140,7 +1136,6 @@ describe('Agent Service', () => { logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, ports: 'testPorts', volumeMappings: 'testVolumeMappings', - imageSnapshot: 'testImageSnapshot', delete: false, deleteWithCleanup: false, catalogItem: { @@ -1167,7 +1162,6 @@ describe('Agent Service', () => { logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, portMappings: 'testPorts', volumeMappings: 'testVolumeMappings', - imageSnapshot: 'testImageSnapshot', delete: false, deleteWithCleanup: false, registryId: 10, @@ -1272,7 +1266,6 @@ describe('Agent Service', () => { logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, portMappings: 'testPorts', volumeMappings: 'testVolumeMappings', - imageSnapshot: 'testImageSnapshot', delete: false, deleteWithCleanup: false, registryId: 10, @@ -1406,137 +1399,6 @@ describe('Agent Service', () => { }) }) - describe('.getAgentStrace()', () => { - const transaction = {} - const error = 'Error!' - - def('uuid', () => 'testUuid') - - def('fog', () => ({ - uuid: $uuid, - })) - - def('microserviceUuid', () => 'testMicroserviceUuid') - def('straceRun', () => 'testStraceRun') - - def('strace', () => ({ - microserviceUuid: $microserviceUuid, - straceRun: $straceRun, - })) - - def('getStracesData', () => ({ - microservice: [{ - strace: $strace, - }], - })) - - def('straceResponse', () => ({ - straceValues: [$strace], - })) - - def('subject', () => $subject.getAgentStrace($fog, transaction)) - - def('getStracesResponse', () => Promise.resolve($getStracesData)) - - beforeEach(() => { - $sandbox.stub(ioFogManager, 'findFogStraces').returns($getStracesResponse) - }) - - it('calls ioFogManager#findFogStraces() with correct args', async () => { - await $subject - expect(ioFogManager.findFogStraces).to.have.been.calledWith({ - uuid: $uuid, - }, transaction) - }) - - context('when ioFogManager#findFogStraces() fails', () => { - def('getStracesResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findFogStraces() succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.deep.equal($straceResponse) - }) - }) - }) - - describe('.updateAgentStrace()', () => { - const transaction = {} - const error = 'Error!' - - def('uuid', () => 'testUuid') - - def('fog', () => ({ - uuid: $uuid, - })) - - def('microserviceUuid', () => 'testMicroserviceUuid') - def('buffer', () => 'testBuffer') - - def('strace', () => ({ - microserviceUuid: $microserviceUuid, - buffer: $buffer, - })) - - def('straceData', () => ({ - straceData: [$strace], - })) - - def('straceResponse', () => ({ - straceValues: [$strace], - })) - - def('subject', () => $subject.updateAgentStrace($straceData, $fog, transaction)) - - def('validatorResponse', () => Promise.resolve(true)) - def('pushBufferResponse', () => Promise.resolve()) - - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(StraceManager, 'pushBufferByMicroserviceUuid').returns($pushBufferResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith($straceData, Validator.schemas.updateAgentStrace) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls StraceManager#pushBufferByMicroserviceUuid() with correct args', async () => { - await $subject - expect(StraceManager.pushBufferByMicroserviceUuid).to.have.been.calledWith($microserviceUuid, $buffer, - transaction) - }) - - context('when StraceManager#pushBufferByMicroserviceUuid() fails', () => { - def('pushBufferResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when StraceManager#pushBufferByMicroserviceUuid() succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - describe('.getAgentChangeVersionCommand()', () => { const transaction = {} const error = 'Error!' @@ -1762,7 +1624,7 @@ describe('Agent Service', () => { def('subject', () => $subject.deleteNode($fog, transaction)) - def('deleteResponse', () => Promise.resolve($getStracesData)) + def('deleteResponse', () => Promise.resolve()) beforeEach(() => { $sandbox.stub(ioFogManager, 'delete').returns($deleteResponse) @@ -1790,53 +1652,4 @@ describe('Agent Service', () => { }) }) - describe('.getImageSnapshot()', () => { - const transaction = {} - const error = 'Error!' - - def('uuid', () => 'testUuid') - - def('fog', () => ({ - uuid: $uuid, - })) - - def('microserviceUuid', () => 'testMicroserviceUuid') - - def('microserviceResponse', () => ({ - uuid: $microserviceUuid, - })) - - def('subject', () => $subject.getImageSnapshot($fog, transaction)) - - def('findResponse', () => Promise.resolve($microserviceResponse)) - - beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findOne').returns($findResponse) - }) - - it('calls MicroserviceManager#delete() with correct args', async () => { - await $subject - expect(MicroserviceManager.findOne).to.have.been.calledWith({ - iofogUuid: $uuid, - imageSnapshot: 'get_image', - }, transaction) - }) - - context('when MicroserviceManager#delete() fails', () => { - def('findResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#delete() succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) - }) - - // TODO - // describe('.putImageSnapshot()', () => { }) diff --git a/test/src/services/diagnostic-service.test.js b/test/src/services/diagnostic-service.test.js deleted file mode 100644 index dd4e2d4b..00000000 --- a/test/src/services/diagnostic-service.test.js +++ /dev/null @@ -1,524 +0,0 @@ -const { expect } = require('chai') -const sinon = require('sinon') -const StraceDiagnosticManager = require('../../../src/data/managers/strace-diagnostics-manager') -const DiagnosticService = require('../../../src/services/diagnostic-service') -const FtpClient = require('ftp') -const fs = require('fs') -const MicroserviceService = require('../../../src/services/microservices-service') -const ChangeTrackingService = require('../../../src/services/change-tracking-service') -const Validator = require('../../../src/schemas') -const MicroserviceManager = require('../../../src/data/managers/microservice-manager') -const Config = require('../../../src/config') - -describe('DiagnosticService Service', () => { - def('subject', () => DiagnosticService) - def('sandbox', () => sinon.createSandbox()) - - const isCLI = true - - afterEach(() => $sandbox.restore()) - - describe('.changeMicroserviceStraceState()', () => { - const transaction = {} - const error = 'Error!' - - const user = 'user!' - - const uuid = 'testUuid' - - const data = { - enable: true, - } - - const straceObj = { - straceRun: data.enable, - microserviceUuid: uuid, - } - - const microservice = { - iofogUuid: 'testIoFogUuid', - } - - def('subject', () => $subject.changeMicroserviceStraceState(uuid, data, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('getMicroserviceResponse', () => Promise.resolve(microservice)) - def('updateOrCreateDiagnosticResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse', () => Promise.resolve()) - - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(MicroserviceService, 'getMicroserviceEndPoint').returns($getMicroserviceResponse) - $sandbox.stub(StraceDiagnosticManager, 'updateOrCreate').returns($updateOrCreateDiagnosticResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(data, Validator.schemas.straceStateUpdate) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls MicroserviceService#getMicroserviceEndPoint() with correct args', async () => { - await $subject - expect(MicroserviceService.getMicroserviceEndPoint).to.have.been.calledWith(uuid, user, isCLI, transaction) - }) - - context('when MicroserviceService#getMicroserviceEndPoint() fails', () => { - def('getMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceService#getMicroserviceEndPoint() succeeds', () => { - it('calls StraceDiagnosticManager#updateOrCreate() with correct args', async () => { - await $subject - expect(StraceDiagnosticManager.updateOrCreate).to.have.been.calledWith({ - microserviceUuid: uuid, - }, straceObj, transaction) - }) - - context('when StraceDiagnosticManager#updateOrCreate() fails', () => { - def('updateOrCreateDiagnosticResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when StraceDiagnosticManager#updateOrCreate() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(microservice.iofogUuid, - ChangeTrackingService.events.diagnostics, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) - - describe('.getMicroserviceStraceData()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const uuid = 'testUuid' - - const data = { - format: 'string', - } - - const microservice = { - iofogUuid: 'testIoFogUuid', - } - - def('subject', () => $subject.getMicroserviceStraceData(uuid, data, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('getMicroserviceResponse', () => Promise.resolve(microservice)) - def('findStraceResponse', () => Promise.resolve({})) - def('configGetResponse', () => Promise.resolve()) - - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(MicroserviceManager, 'findOne').returns($getMicroserviceResponse) - $sandbox.stub(StraceDiagnosticManager, 'findOne').returns($findStraceResponse) - $sandbox.stub(Config, 'get').returns($configGetResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(data, Validator.schemas.straceGetData) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls MicroserviceManager#findOne() with correct args', async () => { - await $subject - const microserviceWhere = isCLI - ? { uuid: uuid } - : { uuid: uuid, userId: user.id } - expect(MicroserviceManager.findOne).to.have.been.calledWith(microserviceWhere, transaction) - }) - - - context('when MicroserviceManager#findOne() fails', () => { - def('getMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#findOne() succeeds', () => { - it('calls StraceDiagnosticManager#findOne() with correct args', async () => { - await $subject - expect(StraceDiagnosticManager.findOne).to.have.been.calledWith({ - microserviceUuid: uuid, - }, transaction) - }) - - context('when StraceDiagnosticManager#findOne() fails', () => { - def('findStraceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when StraceDiagnosticManager#findOne() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Diagnostics:DiagnosticDir') - }) - - context('when Config#get() fails', () => { - def('configGetResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.eventually.have.property('data') - }) - }) - - context('when Config#get() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('data') - }) - }) - }) - }) - }) - }) - - describe('.postMicroserviceStraceDatatoFtp()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const uuid = 'testUuid' - - const data = { - ftpHost: 'testHost', - ftpPort: 5555, - ftpUser: 'testUser', - ftpPass: 'testPass', - ftpDestDir: 'testDir', - } - - const connectionData = { - host: data.ftpHost, - port: data.ftpPort, - user: data.ftpUser, - password: data.ftpPass, - protocol: 'ftp', - } - - const microservice = { - iofogUuid: 'testIoFogUuid', - } - - const dirPath = '/somewhere/on/the/disk' - const straceData = { - buffer: 'data', - } - - def('subject', () => $subject.postMicroserviceStraceDatatoFtp(uuid, data, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('getMicroserviceResponse', () => Promise.resolve(microservice)) - def('findStraceResponse', () => Promise.resolve(straceData)) - def('configGetResponse', () => dirPath) - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(MicroserviceManager, 'findOne').returns($getMicroserviceResponse) - $sandbox.stub(StraceDiagnosticManager, 'findOne').returns($findStraceResponse) - $sandbox.stub(Config, 'get').returns($configGetResponse) - $sandbox.stub(fs, 'existsSync').returns(true) - $sandbox.stub(fs, 'mkdirSync').callsFake(function(dir) {}) - $sandbox.stub(fs, 'writeFileSync').callsFake(function(filePath, data, cb) {}) - $sandbox.stub(fs, 'unlink').callsFake(function(filePath) {}) - $sandbox.stub(FtpClient.prototype, 'connect').withArgs(connectionData).callsFake(function(options) { - this.emit('ready') - }) - $sandbox.stub(FtpClient.prototype, 'put').callsFake((filePath, anotherPath, callback) => { - callback(undefined) - }) - $sandbox.stub(FtpClient.prototype, 'end').callsFake(function(options) {}) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(data, Validator.schemas.stracePostToFtp) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls MicroserviceManager#findOne() with correct args', async () => { - await $subject - const microserviceWhere = isCLI - ? { uuid: uuid } - : { uuid: uuid, userId: user.id } - expect(MicroserviceManager.findOne).to.have.been.calledWith(microserviceWhere, transaction) - }) - - - context('when MicroserviceManager#findOne() fails', () => { - def('getMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#findOne() succeeds', () => { - it('calls StraceDiagnosticManager#findOne() with correct args', async () => { - await $subject - expect(StraceDiagnosticManager.findOne).to.have.been.calledWith({ - microserviceUuid: uuid, - }, transaction) - }) - - context('when StraceDiagnosticManager#findOne() fails', () => { - def('findStraceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when StraceDiagnosticManager#findOne() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Diagnostics:DiagnosticDir') - }) - - context('when Config#get() fails', () => { - def('configGetResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when Config#get() succeeds', () => { - it('calls FtpClient#connect() with correct args', async () => { - await $subject - expect(FtpClient.prototype.connect).to.have.been.calledWith(connectionData) - }) - }) - }) - }) - }) - }) - - describe('.postMicroserviceImageSnapshotCreate()', () => { - const transaction = {} - const error = 'Error!' - - const user = 'user!' - - const microserviceUuid = 'testUuid' - - const microserviceToUpdate = { - imageSnapshot: 'get_image', - } - - const microservice = { - iofogUuid: 'testIoFogUuid', - uuid: 'testMicroserviceUuid', - } - - - def('subject', () => $subject.postMicroserviceImageSnapshotCreate(microserviceUuid, user, isCLI, transaction)) - def('findMicroserviceResponse', () => Promise.resolve(microservice)) - def('updateMicroserviceResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse', () => Promise.resolve()) - - - beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findOneWithDependencies').returns($findMicroserviceResponse) - $sandbox.stub(MicroserviceManager, 'update').returns($updateMicroserviceResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - }) - - it('calls MicroserviceManager#findOneWithDependencies() with correct args', async () => { - await $subject - const where = isCLI ? - { - uuid: microserviceUuid, - } - : - { - uuid: microserviceUuid, - userId: user.id, - } - expect(MicroserviceManager.findOneWithDependencies).to.have.been.calledWith(where, {}, transaction) - }) - - context('when MicroserviceManager#findOneWithDependencies() fails', () => { - def('findMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#findOneWithDependencies() succeeds', () => { - it('calls MicroserviceManager#update() with correct args', async () => { - await $subject - expect(MicroserviceManager.update).to.have.been.calledWith({ - uuid: microservice.uuid, - }, microserviceToUpdate, transaction) - }) - - context('when MicroserviceManager#update() fails', () => { - def('updateMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#update() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(microservice.iofogUuid, - ChangeTrackingService.events.imageSnapshot, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - - describe('.getMicroserviceImageSnapshot()', () => { - const transaction = {} - const error = 'Error!' - - const user = 'user!' - - const microserviceUuid = 'testUuid' - - const microserviceToUpdate = { - imageSnapshot: '', - } - - const microservice = { - iofogUuid: 'testIoFogUuid', - uuid: 'testMicroserviceUuid', - imageSnapshot: 'testImagePath', - } - - - def('subject', () => $subject.getMicroserviceImageSnapshot(microserviceUuid, user, isCLI, transaction)) - def('findMicroserviceResponse', () => Promise.resolve(microservice)) - def('updateMicroserviceResponse', () => Promise.resolve()) - - - beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findOneWithDependencies').returns($findMicroserviceResponse) - $sandbox.stub(MicroserviceManager, 'update').returns($updateMicroserviceResponse) - }) - - it('calls MicroserviceManager#findOneWithDependencies() with correct args', async () => { - await $subject - const where = isCLI ? - { - uuid: microserviceUuid, - } - : - { - uuid: microserviceUuid, - userId: user.id, - } - expect(MicroserviceManager.findOneWithDependencies).to.have.been.calledWith(where, {}, transaction) - }) - - context('when MicroserviceManager#findOneWithDependencies() fails', () => { - def('findMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#findOneWithDependencies() succeeds', () => { - it('calls MicroserviceManager#update() with correct args', async () => { - await $subject - expect(MicroserviceManager.update).to.have.been.calledWith({ - uuid: microservice.uuid, - }, microserviceToUpdate, transaction) - }) - - context('when MicroserviceManager#update() fails', () => { - def('updateMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(microservice.imageSnapshot) - }) - }) - }) - }) -}) diff --git a/test/src/services/iofog-service.test.js b/test/src/services/iofog-service.test.js index c09d5263..c86f6345 100644 --- a/test/src/services/iofog-service.test.js +++ b/test/src/services/iofog-service.test.js @@ -1222,7 +1222,6 @@ describe('ioFog Service', () => { archId: 1, userId: user.id, routerMode: 'none', - edgeResources: [], tags: [] } @@ -1236,7 +1235,7 @@ describe('ioFog Service', () => { def('subject', () => $subject.getFog(fogData, isCLI, transaction)) def('validatorResponse', () => Promise.resolve(true)) - def('findIoFogResponse', () => Promise.resolve({...fog, getRouter: () => Promise.resolve(null), toJSON: () => fog, getEdgeResources: () => Promise.resolve([])})) + def('findIoFogResponse', () => Promise.resolve({...fog, getRouter: () => Promise.resolve(null), toJSON: () => fog})) def('findOneRouterResponse', () => Promise.resolve(null)) def('defaultRouterResponse', () => Promise.resolve(defaultRouter)) @@ -1287,7 +1286,7 @@ describe('ioFog Service', () => { messagingPort: 1234, id: 42 } - def('findIoFogResponse', () => Promise.resolve({...fog, getRouter: () => Promise.resolve(router), getEdgeResources: () => Promise.resolve([]), toJSON: () => fog})) + def('findIoFogResponse', () => Promise.resolve({...fog, getRouter: () => Promise.resolve(router), toJSON: () => fog})) def('findRouterConnectionsResponse', () => Promise.resolve([])) beforeEach(() => { $sandbox.stub(RouterConnectionManager, 'findAllWithRouters').returns($findRouterConnectionsResponse) @@ -1317,7 +1316,7 @@ describe('ioFog Service', () => { edgeRouterPort: 7890, id: 42 } - def('findIoFogResponse', () => Promise.resolve({...fog, getEdgeResources: () => Promise.resolve([]), getRouter: () => Promise.resolve(router), toJSON: () => fog})) + def('findIoFogResponse', () => Promise.resolve({...fog, getRouter: () => Promise.resolve(router), toJSON: () => fog})) it('should return router information', () => { return expect($subject).to.eventually.deep.equal({...fog, routerMode: 'interior', messagingPort: router.messagingPort, edgeRouterPort: router.edgeRouterPort, interRouterPort: router.interRouterPort, upstreamRouters: []}) }) @@ -1374,7 +1373,7 @@ describe('ioFog Service', () => { def('subject', () => $subject.getFogListEndPoint(filters, isCLI, transaction)) def('validatorResponse', () => Promise.resolve(true)) - def('findAllIoFogResponse', () => Promise.resolve(fogs.map(f => ({...f, getRouter: () => Promise.resolve(null), getEdgeResources: () => Promise.resolve([]), toJSON: () => f})))) + def('findAllIoFogResponse', () => Promise.resolve(fogs.map(f => ({...f, getRouter: () => Promise.resolve(null), toJSON: () => f})))) def('findOneRouterResponse', () => Promise.resolve(null)) beforeEach(() => { From fd84fed155004d7c85ea1cfe807008eaf2d9d24a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 05:03:48 +0300 Subject: [PATCH 17/75] Add DNS port 53 to reserved microservice ports. Reject external port 53 on microservice create and update. --- src/helpers/constants.js | 2 +- test/src/services/microservice-port.test.js | 45 +++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 test/src/services/microservice-port.test.js diff --git a/src/helpers/constants.js b/src/helpers/constants.js index 06ef4b64..7afe8da0 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -68,7 +68,7 @@ module.exports = { DEFAULT_NATS_HUB_NAME: 'default-nats-hub', DEFAULT_PROXY_HOST: 'default-proxy-host', - RESERVED_PORTS: [54321, 54322], + RESERVED_PORTS: [54321, 54322, 53], VOLUME_MAPPING_DEFAULT: 'bind', MICROSERVICE_DEFAULT_LOG_SIZE: 1 diff --git a/test/src/services/microservice-port.test.js b/test/src/services/microservice-port.test.js new file mode 100644 index 00000000..4527861c --- /dev/null +++ b/test/src/services/microservice-port.test.js @@ -0,0 +1,45 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const MicroservicePortService = require('../../../src/services/microservice-ports/microservice-port') +const ioFogManager = require('../../../src/data/managers/iofog-manager') +const Constants = require('../../../src/helpers/constants') +const Errors = require('../../../src/helpers/errors') + +describe('Microservice Port Service', () => { + def('sandbox', () => sinon.createSandbox()) + + afterEach(() => $sandbox.restore()) + + describe('reserved ports', () => { + it('includes port 53 in RESERVED_PORTS', () => { + expect(Constants.RESERVED_PORTS).to.include(53) + }) + }) + + describe('.validatePortMappings()', () => { + const transaction = {} + const iofogUuid = 'fog-uuid' + const agent = { + uuid: iofogUuid, + getMicroservice: () => Promise.resolve([]) + } + + def('microserviceData', () => ({ + iofogUuid, + ports: [{ internal: 53, external: 53 }] + })) + def('subject', () => MicroservicePortService.validatePortMappings($microserviceData, transaction)) + + beforeEach(() => { + $sandbox.stub(ioFogManager, 'findOne').resolves(agent) + }) + + it('rejects external port 53 as reserved', () => { + return expect($subject).to.be.rejectedWith( + Errors.ValidationError, + /Port '53' is reserved for internal use/ + ) + }) + }) +}) From 7ea263a64f323883444e75538ae049ee60fd5ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 05:03:51 +0300 Subject: [PATCH 18/75] Validate microservice runtime against agent available runtimes. Auto-inject immutable serviceAccount volume mappings on user microservice create and block user create, update, or delete of those mappings. --- src/schemas/microservice.js | 2 +- src/services/microservices-service.js | 157 +++++++++- .../services/microservices-service.test.js | 274 ++++++++++++++++++ 3 files changed, 431 insertions(+), 2 deletions(-) diff --git a/src/schemas/microservice.js b/src/schemas/microservice.js index 33fba63e..5a29d437 100644 --- a/src/schemas/microservice.js +++ b/src/schemas/microservice.js @@ -245,7 +245,7 @@ const volumeMappings = { 'hostDestination': { 'type': 'string' }, 'containerDestination': { 'type': 'string' }, 'accessMode': { 'type': 'string' }, - 'type': { 'enum': ['volume', 'bind', 'volumeMount'] } + 'type': { 'enum': ['volume', 'bind', 'volumeMount', 'serviceAccount'] } }, 'required': ['hostDestination', 'containerDestination', 'accessMode'], 'additionalProperties': true diff --git a/src/services/microservices-service.js b/src/services/microservices-service.js index 8854313c..5c19148f 100644 --- a/src/services/microservices-service.js +++ b/src/services/microservices-service.js @@ -54,6 +54,10 @@ const constants = require('../helpers/constants') const isEqual = require('lodash/isEqual') const logger = require('../logger') +const SERVICE_ACCOUNT_VOLUME_TYPE = 'serviceAccount' +const SERVICE_ACCOUNT_VOLUME_CONTAINER_DESTINATION = '/var/run/secrets/edgelet.iofog.org/serviceaccount' +const SERVICE_ACCOUNT_VOLUME_ACCESS_MODE = 'ro' + /** * Create or update service account for a microservice * @param {string} microserviceUuid - UUID of the microservice @@ -428,6 +432,88 @@ async function _findFog (microserviceData, isCLI, transaction) { return FogManager.findOne(fogConditions, transaction) } +function _parseFogAvailableRuntimes (fog) { + if (!fog || fog.availableRuntimes == null || fog.availableRuntimes === '') { + return [] + } + if (Array.isArray(fog.availableRuntimes)) { + return fog.availableRuntimes + } + try { + const parsed = JSON.parse(fog.availableRuntimes) + return Array.isArray(parsed) ? parsed : [] + } catch (error) { + return [] + } +} + +function _validateMicroserviceRuntime (runtime, fog) { + if (runtime == null || runtime === '') { + return + } + const availableRuntimes = _parseFogAvailableRuntimes(fog) + if (!availableRuntimes.includes(runtime)) { + const agentLabel = fog.name || fog.uuid || 'agent' + throw new Errors.ValidationError( + `Runtime '${runtime}' is not available on agent '${agentLabel}'` + ) + } +} + +function _isServiceAccountVolumeType (type) { + return type === SERVICE_ACCOUNT_VOLUME_TYPE +} + +function _buildServiceAccountVolumeMapping (microserviceName) { + return { + hostDestination: microserviceName, + containerDestination: SERVICE_ACCOUNT_VOLUME_CONTAINER_DESTINATION, + accessMode: SERVICE_ACCOUNT_VOLUME_ACCESS_MODE, + type: SERVICE_ACCOUNT_VOLUME_TYPE + } +} + +function _rejectUserServiceAccountVolumeMappings (volumeMappings) { + if (!volumeMappings) { + return + } + for (const mapping of volumeMappings) { + const type = mapping.type || VOLUME_MAPPING_DEFAULT + if (_isServiceAccountVolumeType(type)) { + throw new Errors.ValidationError( + 'Volume mappings of type serviceAccount are system-managed and cannot be set by users' + ) + } + } +} + +async function _injectServiceAccountVolume (microservice, transaction) { + const mapping = _buildServiceAccountVolumeMapping(microservice.name) + + const existing = await VolumeMappingManager.findOne({ + microserviceUuid: microservice.uuid, + type: SERVICE_ACCOUNT_VOLUME_TYPE, + containerDestination: SERVICE_ACCOUNT_VOLUME_CONTAINER_DESTINATION + }, transaction) + + if (existing) { + if (existing.hostDestination !== microservice.name) { + await VolumeMappingManager.update( + { uuid: existing.uuid }, + { hostDestination: microservice.name }, + transaction + ) + } + return { created: false, mapping: existing } + } + + const createdMapping = await VolumeMappingManager.create({ + microserviceUuid: microservice.uuid, + ...mapping + }, transaction) + return { created: true, mapping: createdMapping } +} + async function _normalizeMicroserviceNatsConfig (microserviceData, transaction, existingMicroservice = null) { if (Object.prototype.hasOwnProperty.call(microserviceData, 'natsAccess')) { throw new Errors.ValidationError('natsAccess must be provided under natsConfig.natsAccess') @@ -471,6 +557,8 @@ async function createMicroserviceEndPoint (microserviceData, isCLI, transaction) // Set fog uuid for further reference microserviceData.iofogUuid = fog.uuid + _validateMicroserviceRuntime(microserviceData.runtime, fog) + // validate images if (microserviceData.catalogItemId) { // validate catalog item @@ -567,6 +655,8 @@ async function createMicroserviceEndPoint (microserviceData, isCLI, transaction) await _createVolumeMappings(microservice, microserviceData.volumeMappings, transaction) } + await _injectServiceAccountVolume(microservice, transaction) + if (microserviceData.iofogUuid) { await _updateChangeTracking(false, microserviceData.iofogUuid, transaction) } @@ -611,6 +701,7 @@ async function createMicroserviceEndPoint (microserviceData, isCLI, transaction) } function _validateVolumeMappings (volumeMappings) { + _rejectUserServiceAccountVolumeMappings(volumeMappings) if (volumeMappings) { for (const mapping of volumeMappings) { mapping.type = mapping.type || VOLUME_MAPPING_DEFAULT @@ -930,6 +1021,14 @@ async function updateSystemMicroserviceEndPoint (microserviceUuid, microserviceD images = await microservice.getImages() } _validateImageFogType(microserviceData, fog, images) + const shouldValidateRuntime = microserviceDataUpdate.runtime !== undefined || + (microserviceDataUpdate.iofogUuid && microserviceDataUpdate.iofogUuid !== microservice.iofogUuid) + if (shouldValidateRuntime) { + const runtimeToValidate = microserviceDataUpdate.runtime !== undefined + ? microserviceDataUpdate.runtime + : microservice.runtime + _validateMicroserviceRuntime(runtimeToValidate, fog) + } } // Set rebuild flag if needed @@ -965,6 +1064,11 @@ async function updateSystemMicroserviceEndPoint (microserviceUuid, microserviceD if (microserviceDataUpdate.volumeMappings) { await _updateVolumeMappings(microserviceDataUpdate.volumeMappings, microserviceUuid, transaction) + } else if (microserviceDataUpdate.name && microserviceDataUpdate.name !== microservice.name) { + await _injectServiceAccountVolume( + { uuid: microserviceUuid, name: microserviceDataUpdate.name }, + transaction + ) } if (microserviceDataUpdate.env) { @@ -1218,6 +1322,14 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i images = await microservice.getImages() } _validateImageFogType(microserviceData, fog, images) + const shouldValidateRuntime = microserviceDataUpdate.runtime !== undefined || + (microserviceDataUpdate.iofogUuid && microserviceDataUpdate.iofogUuid !== microservice.iofogUuid) + if (shouldValidateRuntime) { + const runtimeToValidate = microserviceDataUpdate.runtime !== undefined + ? microserviceDataUpdate.runtime + : microservice.runtime + _validateMicroserviceRuntime(runtimeToValidate, fog) + } } // Set rebuild flag if needed @@ -1253,6 +1365,11 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i if (microserviceDataUpdate.volumeMappings) { await _updateVolumeMappings(microserviceDataUpdate.volumeMappings, microserviceUuid, transaction) + } else if (microserviceDataUpdate.name && microserviceDataUpdate.name !== microservice.name) { + await _injectServiceAccountVolume( + { uuid: microserviceUuid, name: microserviceDataUpdate.name }, + transaction + ) } if (microserviceDataUpdate.env) { @@ -1812,6 +1929,12 @@ async function createVolumeMappingEndPoint (microserviceUuid, volumeMappingData, const type = volumeMappingData.type || VOLUME_MAPPING_DEFAULT + if (_isServiceAccountVolumeType(type)) { + throw new Errors.ValidationError( + 'Volume mappings of type serviceAccount are system-managed and cannot be created by users' + ) + } + const volumeMapping = await VolumeMappingManager.findOne({ microserviceUuid: microserviceUuid, hostDestination: volumeMappingData.hostDestination, @@ -1855,6 +1978,12 @@ async function createSystemVolumeMappingEndPoint (microserviceUuid, volumeMappin const type = volumeMappingData.type || VOLUME_MAPPING_DEFAULT + if (_isServiceAccountVolumeType(type)) { + throw new Errors.ValidationError( + 'Volume mappings of type serviceAccount are system-managed and cannot be created by users' + ) + } + const volumeMapping = await VolumeMappingManager.findOne({ microserviceUuid: microserviceUuid, hostDestination: volumeMappingData.hostDestination, @@ -1899,6 +2028,17 @@ async function deleteVolumeMappingEndPoint (microserviceUuid, volumeMappingUuid, microserviceUuid: microserviceUuid } + const volumeMapping = await VolumeMappingManager.findOne(volumeMappingWhere, transaction) + if (!volumeMapping) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_VOLUME_MAPPING_UUID, volumeMappingUuid)) + } + + if (_isServiceAccountVolumeType(volumeMapping.type)) { + throw new Errors.ValidationError( + 'Volume mappings of type serviceAccount are system-managed and cannot be deleted by users' + ) + } + const affectedRows = await VolumeMappingManager.delete(volumeMappingWhere, transaction) if (affectedRows === 0) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_VOLUME_MAPPING_UUID, volumeMappingUuid)) @@ -1920,6 +2060,17 @@ async function deleteSystemVolumeMappingEndPoint (microserviceUuid, volumeMappin microserviceUuid: microserviceUuid } + const volumeMapping = await VolumeMappingManager.findOne(volumeMappingWhere, transaction) + if (!volumeMapping) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_VOLUME_MAPPING_UUID, volumeMappingUuid)) + } + + if (_isServiceAccountVolumeType(volumeMapping.type)) { + throw new Errors.ValidationError( + 'Volume mappings of type serviceAccount are system-managed and cannot be deleted by users' + ) + } + const affectedRows = await VolumeMappingManager.delete(volumeMappingWhere, transaction) if (affectedRows === 0) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_VOLUME_MAPPING_UUID, volumeMappingUuid)) @@ -2200,6 +2351,8 @@ async function _updateVolumeMappings (volumeMappings, microserviceUuid, transact await VolumeMappingManager.create(volumeMappingObj, transaction) } + + await _injectServiceAccountVolume(microservice, transaction) } async function _updateImages (images, microserviceUuid, transaction) { @@ -2716,5 +2869,7 @@ module.exports = { deleteSystemExecEndPoint: TransactionDecorator.generateTransaction(deleteSystemExecEndPoint), startMicroserviceEndPoint: TransactionDecorator.generateTransaction(startMicroserviceEndPoint), stopMicroserviceEndPoint: TransactionDecorator.generateTransaction(stopMicroserviceEndPoint), - reconcileNatsForApplication: TransactionDecorator.generateTransaction(reconcileNatsForApplication, bypassOptions) + reconcileNatsForApplication: TransactionDecorator.generateTransaction(reconcileNatsForApplication, bypassOptions), + injectServiceAccountVolume: _injectServiceAccountVolume, + createOrUpdateServiceAccountForMicroservice: _createOrUpdateServiceAccountForMicroservice } diff --git a/test/src/services/microservices-service.test.js b/test/src/services/microservices-service.test.js index c3c86701..81add8b1 100644 --- a/test/src/services/microservices-service.test.js +++ b/test/src/services/microservices-service.test.js @@ -1899,4 +1899,278 @@ describe('Microservices Service', () => { }) }) }) + + describe('microservice runtime validation', () => { + const transaction = {} + const application = { name: 'my-app', id: 42, active: true } + const fogUuid = 'fog-uuid' + + describe('.createMicroserviceEndPoint()', () => { + def('fog', () => ({ + uuid: fogUuid, + name: 'agent-1', + fogTypeId: 1, + availableRuntimes: '["docker"]' + })) + def('microserviceData', () => ({ + name: 'my-msvc', + application: application.name, + iofogUuid: fogUuid, + runtime: 'edgelet', + images: [{ fogTypeId: 1, containerImage: 'hello-world' }] + })) + def('subject', () => MicroservicesService.createMicroserviceEndPoint($microserviceData, isCLI, transaction)) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ApplicationManager, 'findOne').resolves(application) + $sandbox.stub(ioFogManager, 'findOne').resolves($fog) + }) + + context('when runtime is not in agent availableRuntimes', () => { + it('rejects the request', () => { + return expect($subject).to.be.rejectedWith( + Errors.ValidationError, + /Runtime 'edgelet' is not available on agent 'agent-1'/ + ) + }) + }) + + context('when agent has no availableRuntimes and runtime is set', () => { + def('fog', () => ({ + uuid: fogUuid, + name: 'agent-1', + fogTypeId: 1, + availableRuntimes: '' + })) + + it('rejects the request', () => { + return expect($subject).to.be.rejectedWith( + Errors.ValidationError, + /Runtime 'edgelet' is not available on agent 'agent-1'/ + ) + }) + }) + }) + }) + + describe('serviceAccount volume immutability', () => { + const transaction = {} + const microserviceUuid = 'msvc-uuid' + const microserviceName = 'my-msvc' + const saContainerDestination = '/var/run/secrets/edgelet.iofog.org/serviceaccount' + const MicroservicePortService = require('../../../src/services/microservice-ports/microservice-port') + const MicroserviceExecStatusManager = require('../../../src/data/managers/microservice-exec-status-manager') + const CatalogItemImageManager = require('../../../src/data/managers/catalog-item-image-manager') + const RbacRoleManager = require('../../../src/data/managers/rbac-role-manager') + const RbacServiceAccountManager = require('../../../src/data/managers/rbac-service-account-manager') + + describe('.createMicroserviceEndPoint()', () => { + const application = { name: 'my-app', id: 42, active: true } + const fog = { uuid: 'fog-uuid', name: 'agent-1', fogTypeId: 1, availableRuntimes: '["docker"]' } + + def('subject', () => MicroservicesService.createMicroserviceEndPoint($microserviceData, isCLI, transaction)) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ApplicationManager, 'findOne').resolves(application) + $sandbox.stub(ioFogManager, 'findOne').resolves(fog) + $sandbox.stub(MicroserviceManager, 'findOne').callsFake((where) => { + if (where && where.uuid === microserviceUuid) { + return Promise.resolve({ + uuid: microserviceUuid, + name: microserviceName, + applicationId: application.id + }) + } + return Promise.resolve(null) + }) + $sandbox.stub(AppHelper, 'generateRandomString').returns(microserviceUuid) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((obj) => obj) + $sandbox.stub(MicroserviceManager, 'create').resolves({ + uuid: microserviceUuid, + name: microserviceName, + applicationId: application.id, + iofogUuid: fog.uuid + }) + $sandbox.stub(MicroservicePortService, 'validatePortMappings').resolves() + $sandbox.stub(RegistryManager, 'findOne').resolves({ id: 1 }) + $sandbox.stub(MicroservicePortManager, 'findOne').resolves(null) + $sandbox.stub(MicroservicePortManager, 'create').resolves({ id: 1 }) + $sandbox.stub(MicroserviceStatusManager, 'create').resolves() + $sandbox.stub(MicroserviceExecStatusManager, 'create').resolves() + $sandbox.stub(CatalogItemImageManager, 'bulkCreate').resolves() + $sandbox.stub(ChangeTrackingService, 'update').resolves() + $sandbox.stub(VolumeMappingManager, 'bulkCreate').resolves() + $sandbox.stub(VolumeMappingManager, 'findOne').resolves(null) + $sandbox.stub(VolumeMappingManager, 'create').resolves({ uuid: 'sa-volume-uuid' }) + $sandbox.stub(RbacRoleManager, 'getRoleWithRules').resolves({ name: 'microservice' }) + $sandbox.stub(RbacServiceAccountManager, 'findOneByMicroserviceUuid').resolves(null) + $sandbox.stub(RbacServiceAccountManager, 'createServiceAccount').resolves({ name: microserviceName }) + }) + + context('when user supplies a serviceAccount volume mapping', () => { + def('microserviceData', () => ({ + name: microserviceName, + application: application.name, + iofogUuid: fog.uuid, + images: [{ fogTypeId: 1, containerImage: 'hello-world' }], + volumeMappings: [{ + hostDestination: microserviceName, + containerDestination: saContainerDestination, + accessMode: 'ro', + type: 'serviceAccount' + }] + })) + + it('rejects the request', () => { + return expect($subject).to.be.rejectedWith( + Errors.ValidationError, + /serviceAccount are system-managed/ + ) + }) + }) + + context('when user does not supply a serviceAccount volume mapping', () => { + def('microserviceData', () => ({ + name: microserviceName, + application: application.name, + iofogUuid: fog.uuid, + images: [{ fogTypeId: 1, containerImage: 'hello-world' }], + volumeMappings: [{ + hostDestination: '/var/dest', + containerDestination: '/var/dest', + accessMode: 'rw', + type: 'bind' + }] + })) + + it('auto-injects the serviceAccount volume after user mappings', async () => { + await $subject + expect(VolumeMappingManager.create).to.have.been.calledWith({ + microserviceUuid, + hostDestination: microserviceName, + containerDestination: saContainerDestination, + accessMode: 'ro', + type: 'serviceAccount' + }, transaction) + }) + }) + }) + + describe('.createVolumeMappingEndPoint()', () => { + def('volumeMappingData', () => ({ + hostDestination: microserviceName, + containerDestination: saContainerDestination, + accessMode: 'ro', + type: 'serviceAccount' + })) + def('subject', () => MicroservicesService.createVolumeMappingEndPoint( + microserviceUuid, + $volumeMappingData, + isCLI, + transaction + )) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(MicroserviceManager, 'findMicroserviceOnGet').resolves({ uuid: microserviceUuid }) + }) + + it('rejects serviceAccount volume creation by users', () => { + return expect($subject).to.be.rejectedWith( + Errors.ValidationError, + /serviceAccount are system-managed/ + ) + }) + }) + + describe('.deleteVolumeMappingEndPoint()', () => { + const saVolumeUuid = 'sa-volume-uuid' + def('subject', () => MicroservicesService.deleteVolumeMappingEndPoint( + microserviceUuid, + saVolumeUuid, + isCLI, + transaction + )) + + beforeEach(() => { + $sandbox.stub(MicroserviceManager, 'findOne').resolves({ uuid: microserviceUuid }) + $sandbox.stub(VolumeMappingManager, 'findOne').resolves({ + uuid: saVolumeUuid, + type: 'serviceAccount' + }) + $sandbox.stub(VolumeMappingManager, 'delete').resolves(1) + }) + + it('rejects deletion of serviceAccount volume mappings', () => { + return expect($subject).to.be.rejectedWith( + Errors.ValidationError, + /serviceAccount are system-managed/ + ) + }) + + it('does not call VolumeMappingManager#delete()', async () => { + try { + await $subject + } catch (error) { + // expected + } + expect(VolumeMappingManager.delete).to.not.have.been.called + }) + }) + + describe('.updateMicroserviceEndPoint()', () => { + const microservice = { + uuid: microserviceUuid, + name: microserviceName, + applicationId: 42, + registryId: 1, + iofogUuid: 'fog-uuid', + schedule: 0, + catalogItem: null, + getImages: () => Promise.resolve([{ fogTypeId: 1, containerImage: 'hello-world' }]), + getPorts: () => Promise.resolve([]) + } + + def('subject', () => MicroservicesService.updateMicroserviceEndPoint( + microserviceUuid, + $microserviceData, + isCLI, + transaction + )) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((obj) => obj) + $sandbox.stub(MicroserviceManager, 'findOne').resolves(microservice) + $sandbox.stub(MicroserviceManager, 'findOneWithCategory').resolves(microservice) + $sandbox.stub(CatalogItemImageManager, 'findAll').resolves([]) + $sandbox.stub(ApplicationManager, 'findOne').resolves({ id: 42, natsAccess: false }) + $sandbox.stub(RegistryManager, 'findOne').resolves({ id: 1 }) + $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid: 'fog-uuid', fogTypeId: 1 }) + $sandbox.stub(MicroserviceExtraHostManager, 'findAll').resolves([]) + $sandbox.stub(MicroserviceManager, 'updateAndFind').resolves(microservice) + $sandbox.stub(ChangeTrackingService, 'update').resolves() + }) + + context('when patch includes a serviceAccount volume mapping', () => { + def('microserviceData', () => ({ + volumeMappings: [{ + hostDestination: microserviceName, + containerDestination: saContainerDestination, + accessMode: 'ro', + type: 'serviceAccount' + }] + })) + + it('rejects the update', () => { + return expect($subject).to.be.rejectedWith( + Errors.ValidationError, + /serviceAccount are system-managed/ + ) + }) + }) + }) + }) }) From 5e278ec98c4b837e2a884e910332d0d09b8e3ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 05:03:56 +0300 Subject: [PATCH 19/75] Inject service account volumes into router and NATS system microservices. Create or update RBAC service accounts for system microservices and set microserviceList change tracking when a new volume mapping is backfilled. --- src/services/nats-service.js | 15 +++++++++++++++ src/services/router-service.js | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/services/nats-service.js b/src/services/nats-service.js index d1cd72a8..92b67745 100644 --- a/src/services/nats-service.js +++ b/src/services/nats-service.js @@ -39,6 +39,7 @@ const NatsUserManager = require('../data/managers/nats-user-manager') const ApplicationManager = require('../data/managers/application-manager') const NatsAuthService = require('./nats-auth-service') const ChangeTrackingService = require('./change-tracking-service') +const MicroservicesService = require('./microservices-service') const FogManager = require('../data/managers/iofog-manager') const databaseProvider = require('../data/providers/database-factory') const config = require('../config') @@ -810,6 +811,20 @@ async function _ensureNatsMicroservice (fog, mode, transaction) { await MicroserviceHealthCheckManager.create(healthCheckData, transaction) } + const { created: saVolumeCreated } = await MicroservicesService.injectServiceAccountVolume( + microservice, + transaction + ) + await MicroservicesService.createOrUpdateServiceAccountForMicroservice( + microservice.uuid, + microservice.name, + null, + transaction + ) + if (saVolumeCreated) { + microservice._volumeMappingCreated = true + } + return microservice } diff --git a/src/services/router-service.js b/src/services/router-service.js index c80fcbe9..1b805632 100644 --- a/src/services/router-service.js +++ b/src/services/router-service.js @@ -34,6 +34,7 @@ const FogManager = require('../data/managers/iofog-manager') const config = require('../config') const VolumeMountService = require('./volume-mount-service') const VolumeMappingManager = require('../data/managers/volume-mapping-manager') +const MicroservicesService = require('./microservices-service') const { ensureSystemApplication, getSystemMicroserviceName @@ -615,6 +616,24 @@ async function _ensureRouterSslVolumeMountsAndMappings (iofogUuid, routerMicrose } } } + + const routerMicroservice = await MicroserviceManager.findOne({ uuid: routerMicroserviceUuid }, transaction) + if (routerMicroservice) { + const { created: saVolumeCreated } = await MicroservicesService.injectServiceAccountVolume( + routerMicroservice, + transaction + ) + await MicroservicesService.createOrUpdateServiceAccountForMicroservice( + routerMicroservice.uuid, + routerMicroservice.name, + null, + transaction + ) + if (saVolumeCreated) { + await MicroserviceManager.update({ uuid: routerMicroserviceUuid }, { rebuild: true }, transaction) + await ChangeTrackingService.update(iofogUuid, ChangeTrackingService.events.microserviceList, transaction) + } + } } async function getNetworkRouter (networkRouterId, transaction) { From d414ee51899a077c75849be861053379c10aa274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 05:04:02 +0300 Subject: [PATCH 20/75] Migrate microservice and agent-admin roles to edgelet.iofog.org/v1. Consolidate default microservice role rules for config, auth, GPS, and control. --- src/config/rbac-system-roles.js | 40 +++++++++------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/src/config/rbac-system-roles.js b/src/config/rbac-system-roles.js index 60b252e0..999c9404 100644 --- a/src/config/rbac-system-roles.js +++ b/src/config/rbac-system-roles.js @@ -95,23 +95,14 @@ module.exports = { }, AGENT_ADMIN_ROLE: { name: 'agent-admin', - apiVersion: 'agent.datasance.com/v3', + apiVersion: 'edgelet.iofog.org/v1', kind: 'Role', get namespace () { return getNamespace() }, rules: [ { - // Wildcard covers all agent API resources and verbs - // This includes all Microservice role permissions plus: - // - status (get) - // - info (get) - // - version (get) - // - provision (post) - // - deprovision (delete) - // - config (post) - // - prune (post) - apiGroups: ['agent.datasance.com/v3'], + apiGroups: ['edgelet.iofog.org/v1'], resources: ['*'], verbs: ['*'] } @@ -119,32 +110,21 @@ module.exports = { }, MICROSERVICE_ROLE: { name: 'microservice', - apiVersion: 'agent.datasance.com/v3', + apiVersion: 'edgelet.iofog.org/v1', kind: 'Role', get namespace () { return getNamespace() }, rules: [ { - apiGroups: ['agent.datasance.com/v3'], - resources: ['gps'], - verbs: ['get', 'patch'] - }, - { - apiGroups: ['agent.datasance.com/v3'], - resources: ['config'], - verbs: ['get'] - }, - { - apiGroups: ['agent.datasance.com/v3'], - resources: ['log'], - verbs: ['patch'] - }, - { - apiGroups: ['agent.datasance.com/v3'], - resources: ['control'], + apiGroups: ['edgelet.iofog.org/v1'], + resources: [ + 'microservices/config/self', + 'auth/whoami', + 'system/gps', + 'microservices/control/self' + ], verbs: ['get'] - // Note: WebSocket 'get' for control is handled separately by agent } ] } From e3dbfe0bd77744e3133587634c023a1d95c4e33d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 05:04:08 +0300 Subject: [PATCH 21/75] Notify hosting agents when RBAC service account or role rules change. Fan out microserviceList change tracking to every distinct agent affected by service account or role updates; role binding updates do not notify. --- src/services/rbac-service.js | 44 +++++--- test/src/services/rbac-service.test.js | 141 +++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 14 deletions(-) create mode 100644 test/src/services/rbac-service.test.js diff --git a/src/services/rbac-service.js b/src/services/rbac-service.js index c7b062df..fc74aae9 100644 --- a/src/services/rbac-service.js +++ b/src/services/rbac-service.js @@ -34,25 +34,41 @@ function validateNotSystemRole (roleName) { } /** - * Notify microservice when a service account linked to a microservice is updated - * @param {Object} serviceAccount - Service account object (must have microserviceUuid if linked to a microservice) + * Set microserviceList change tracking on every distinct agent hosting an MS linked to the given SAs. + * R22: agent list embeds SA rules from SA.roleRef → Role.rules only; SA or role mutations must refresh the list. + * @param {Array} serviceAccounts - Service account records (may omit microserviceUuid for app-scoped SAs) * @param {object} transaction - Database transaction */ -async function _notifyMicroservicesForServiceAccountUpdate (serviceAccount, transaction) { - const microserviceUuid = serviceAccount.microserviceUuid || (serviceAccount.get && serviceAccount.get('microserviceUuid')) - if (!microserviceUuid) { - return - } - try { - const microservice = await MicroserviceManager.findOne({ uuid: microserviceUuid }, transaction) - if (microservice && microservice.iofogUuid) { - await ChangeTrackingService.update(microservice.iofogUuid, ChangeTrackingService.events.microserviceFull, transaction) +async function _setMicroserviceListChangeTrackingForServiceAccounts (serviceAccounts, transaction) { + const iofogUuids = new Set() + for (const serviceAccount of serviceAccounts) { + const microserviceUuid = serviceAccount.microserviceUuid || (serviceAccount.get && serviceAccount.get('microserviceUuid')) + if (!microserviceUuid) { + continue } - } catch (error) { - logger.error(`Failed to notify microservice for service account update (microserviceUuid: ${microserviceUuid}):`, error.message) + try { + const microservice = await MicroserviceManager.findOne({ uuid: microserviceUuid }, transaction) + if (microservice && microservice.iofogUuid) { + iofogUuids.add(microservice.iofogUuid) + } + } catch (error) { + logger.error(`Failed to resolve agent for service account update (microserviceUuid: ${microserviceUuid}):`, error.message) + } + } + for (const iofogUuid of iofogUuids) { + await ChangeTrackingService.update(iofogUuid, ChangeTrackingService.events.microserviceList, transaction) } } +/** + * Notify hosting agent when a single service account linked to a microservice is updated. + * @param {Object} serviceAccount - Service account object (must have microserviceUuid if linked to a microservice) + * @param {object} transaction - Database transaction + */ +async function _notifyMicroservicesForServiceAccountUpdate (serviceAccount, transaction) { + await _setMicroserviceListChangeTrackingForServiceAccounts([serviceAccount], transaction) +} + // Role Management async function listRolesEndpoint (transaction) { const roles = await RbacRoleManager.listRoles(transaction) @@ -136,8 +152,8 @@ async function updateRoleEndpoint (name, roleData, transaction) { roleRef: sa.roleRef }, transaction) } - await _notifyMicroservicesForServiceAccountUpdate(sa, transaction) } + await _setMicroserviceListChangeTrackingForServiceAccounts(serviceAccounts, transaction) } return { diff --git a/test/src/services/rbac-service.test.js b/test/src/services/rbac-service.test.js new file mode 100644 index 00000000..1d12d9a5 --- /dev/null +++ b/test/src/services/rbac-service.test.js @@ -0,0 +1,141 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const RbacService = require('../../../src/services/rbac-service') +const RbacRoleManager = require('../../../src/data/managers/rbac-role-manager') +const RbacRoleBindingManager = require('../../../src/data/managers/rbac-role-binding-manager') +const RbacServiceAccountManager = require('../../../src/data/managers/rbac-service-account-manager') +const MicroserviceManager = require('../../../src/data/managers/microservice-manager') +const ApplicationManager = require('../../../src/data/managers/application-manager') +const Validator = require('../../../src/schemas') +const ChangeTrackingService = require('../../../src/services/change-tracking-service') + +describe('Rbac Service', () => { + def('sandbox', () => sinon.createSandbox()) + + afterEach(() => $sandbox.restore()) + + describe('R22 change tracking', () => { + const transaction = {} + + describe('.updateServiceAccountEndpoint()', () => { + const appName = 'my-app' + const saName = 'my-msvc' + const iofogUuid = 'agent-uuid-1' + const microserviceUuid = 'msvc-uuid-1' + const saData = { roleRef: 'custom-role' } + + def('subject', () => RbacService.updateServiceAccountEndpoint(appName, saName, saData, transaction)) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(RbacServiceAccountManager, 'updateServiceAccount').resolves({ + name: saName, + microserviceUuid, + roleRef: 'custom-role' + }) + $sandbox.stub(MicroserviceManager, 'findOne').resolves({ + uuid: microserviceUuid, + iofogUuid + }) + $sandbox.stub(ChangeTrackingService, 'update').resolves() + }) + + it('sets microserviceList change tracking on the hosting agent', async () => { + await $subject + expect(ChangeTrackingService.update).to.have.been.calledOnceWith( + iofogUuid, + ChangeTrackingService.events.microserviceList, + transaction + ) + }) + + context('when service account is not linked to a microservice', () => { + beforeEach(() => { + RbacServiceAccountManager.updateServiceAccount.resolves({ + name: saName, + microserviceUuid: null + }) + }) + + it('does not set microserviceList change tracking', async () => { + await $subject + expect(ChangeTrackingService.update).to.not.have.been.called + }) + }) + }) + + describe('.updateRoleEndpoint()', () => { + const roleName = 'custom-role' + const roleId = 99 + const roleData = { + rules: [{ + apiGroups: ['edgelet.iofog.org/v1'], + resources: ['microservices/config/self'], + verbs: ['get', 'patch'] + }] + } + const agentOne = 'agent-uuid-1' + const agentTwo = 'agent-uuid-2' + + def('subject', () => RbacService.updateRoleEndpoint(roleName, roleData, transaction)) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(RbacRoleManager, 'isSystemRole').returns(false) + $sandbox.stub(RbacRoleManager, 'findOne') + .onFirstCall().resolves({ id: roleId, name: roleName }) + .onSecondCall().resolves({ id: roleId, name: roleName }) + $sandbox.stub(RbacRoleManager, 'updateRole').resolves({ name: roleName }) + $sandbox.stub(RbacRoleBindingManager, 'findAll').resolves([]) + $sandbox.stub(RbacServiceAccountManager, 'findAll').resolves([ + { name: 'sa1', microserviceUuid: 'msvc-1', applicationId: 1, roleRef: roleName }, + { name: 'sa2', microserviceUuid: 'msvc-2', applicationId: 1, roleRef: roleName }, + { name: 'sa3', microserviceUuid: 'msvc-3', applicationId: 2, roleRef: roleName } + ]) + $sandbox.stub(ApplicationManager, 'findOne') + .withArgs({ id: 1 }).resolves({ name: 'app1' }) + .withArgs({ id: 2 }).resolves({ name: 'app2' }) + $sandbox.stub(RbacServiceAccountManager, 'updateServiceAccount').resolves({}) + $sandbox.stub(MicroserviceManager, 'findOne') + .withArgs({ uuid: 'msvc-1' }, transaction).resolves({ uuid: 'msvc-1', iofogUuid: agentOne }) + .withArgs({ uuid: 'msvc-2' }, transaction).resolves({ uuid: 'msvc-2', iofogUuid: agentOne }) + .withArgs({ uuid: 'msvc-3' }, transaction).resolves({ uuid: 'msvc-3', iofogUuid: agentTwo }) + $sandbox.stub(ChangeTrackingService, 'update').resolves() + }) + + it('sets microserviceList change tracking on every distinct hosting agent', async () => { + await $subject + expect(ChangeTrackingService.update).to.have.been.calledTwice + expect(ChangeTrackingService.update).to.have.been.calledWith( + agentOne, + ChangeTrackingService.events.microserviceList, + transaction + ) + expect(ChangeTrackingService.update).to.have.been.calledWith( + agentTwo, + ChangeTrackingService.events.microserviceList, + transaction + ) + }) + }) + + describe('.updateRoleBindingEndpoint()', () => { + const bindingName = 'developer-binding' + const bindingData = { roleRef: 'developer' } + + def('subject', () => RbacService.updateRoleBindingEndpoint(bindingName, bindingData, transaction)) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(RbacRoleBindingManager, 'updateRoleBinding').resolves({ name: bindingName }) + $sandbox.stub(ChangeTrackingService, 'update').resolves() + }) + + it('does not set microserviceList change tracking', async () => { + await $subject + expect(ChangeTrackingService.update).to.not.have.been.called + }) + }) + }) +}) From 0ab9bd3dc1f3a59995a0f1d48519a82974fb96fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 06:50:19 +0300 Subject: [PATCH 22/75] Ensure default router and NATS local CAs at controller startup. Add PKI secret name constants and create missing central local CAs during init. --- src/helpers/constants.js | 9 +++++++++ src/init.js | 4 ++++ src/services/certificate-service.js | 23 ++++++++++++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/helpers/constants.js b/src/helpers/constants.js index 7afe8da0..939afcfe 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -68,6 +68,15 @@ module.exports = { DEFAULT_NATS_HUB_NAME: 'default-nats-hub', DEFAULT_PROXY_HOST: 'default-proxy-host', + DEFAULT_ROUTER_LOCAL_CA: 'default-router-local-ca', + DEFAULT_NATS_LOCAL_CA: 'default-nats-local-ca', + ROUTER_SITE_CA: 'router-site-ca', + NATS_SITE_CA: 'nats-site-ca', + + ROUTER_BRIDGE_DNS_SAN: 'router.default.svc.bridge.local', + NATS_BRIDGE_DNS_SAN: 'nats.default.svc.bridge.local', + DEFAULT_ROUTER_K8S_SERVICE: 'router', + RESERVED_PORTS: [54321, 54322, 53], VOLUME_MAPPING_DEFAULT: 'bind', diff --git a/src/init.js b/src/init.js index dc5b76ff..7bcc409c 100644 --- a/src/init.js +++ b/src/init.js @@ -40,6 +40,10 @@ async function initialize () { logger.info('Initializing database...') await db.initDB(true) + const CertificateService = require('./services/certificate-service') + logger.info('Ensuring central router and NATS local CAs...') + await CertificateService.ensureCentralLocalCAs({ fakeTransaction: true }) + logger.info('Initialization completed successfully') return true } catch (error) { diff --git a/src/services/certificate-service.js b/src/services/certificate-service.js index e0e6b35e..217f2c14 100644 --- a/src/services/certificate-service.js +++ b/src/services/certificate-service.js @@ -8,6 +8,7 @@ const AppHelper = require('../helpers/app-helper') const Validator = require('../schemas/index') const { generateSelfSignedCA, storeCA, generateCertificate } = require('../utils/cert') const config = require('../config') +const Constants = require('../helpers/constants') const forge = require('node-forge') // Helper function to check Kubernetes environment @@ -152,6 +153,25 @@ async function createCAEndpoint (caData, transaction) { } } +async function ensureCentralLocalCAs (transaction) { + for (const name of [Constants.DEFAULT_ROUTER_LOCAL_CA, Constants.DEFAULT_NATS_LOCAL_CA]) { + try { + await getCAEndpoint(name, transaction) + } catch (err) { + if (err.name === 'NotFoundError') { + await createCAEndpoint({ + name, + subject: name, + expiration: 60, + type: 'self-signed' + }, transaction) + } else if (err.name !== 'ConflictError') { + throw err + } + } + } +} + async function getCAEndpoint (name, transaction) { const certRecord = await CertificateManager.findCertificateByName(name, transaction) @@ -624,5 +644,6 @@ module.exports = { listCertificatesEndpoint: TransactionDecorator.generateTransaction(listCertificatesEndpoint), deleteCertificateEndpoint: TransactionDecorator.generateTransaction(deleteCertificateEndpoint), renewCertificateEndpoint: TransactionDecorator.generateTransaction(renewCertificateEndpoint), - listExpiringCertificatesEndpoint: TransactionDecorator.generateTransaction(listExpiringCertificatesEndpoint) + listExpiringCertificatesEndpoint: TransactionDecorator.generateTransaction(listExpiringCertificatesEndpoint), + ensureCentralLocalCAs: TransactionDecorator.generateTransaction(ensureCentralLocalCAs) } From 07d1ea94cc4b461c3e8e9538f065bfddfeebd2f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 06:50:25 +0300 Subject: [PATCH 23/75] Sign agent router and NATS certificates with central local CAs. Use shared DNS SAN host builders for router messaging and NATS MQTT certs. Recreate router and NATS certificates when agent host or router mode changes, and emit volumeMounts change tracking when certs are renewed. --- src/helpers/cert-dns-sans.js | 68 ++++++++++++++++++ src/services/iofog-service.js | 126 ++++++++++++++++++++++------------ src/services/nats-service.js | 19 ++--- 3 files changed, 162 insertions(+), 51 deletions(-) create mode 100644 src/helpers/cert-dns-sans.js diff --git a/src/helpers/cert-dns-sans.js b/src/helpers/cert-dns-sans.js new file mode 100644 index 00000000..15558241 --- /dev/null +++ b/src/helpers/cert-dns-sans.js @@ -0,0 +1,68 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const config = require('../config') +const Constants = require('./constants') + +function getControllerNamespace () { + return process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'datasance') +} + +function buildRouterLocalCertificateHostList (fogData, { isDefaultRouter = false } = {}) { + const hosts = new Set() + const defaultHosts = [ + 'localhost', + '127.0.0.1', + 'host.docker.internal', + 'host.containers.internal', + 'iofog', + 'service.local' + ] + defaultHosts.forEach(host => hosts.add(host)) + if (fogData.host) hosts.add(fogData.host) + if (fogData.ipAddress) hosts.add(fogData.ipAddress) + if (fogData.ipAddressExternal) hosts.add(fogData.ipAddressExternal) + hosts.add(Constants.ROUTER_BRIDGE_DNS_SAN) + if (isDefaultRouter) { + const namespace = getControllerNamespace() + if (namespace) { + hosts.add(`${Constants.DEFAULT_ROUTER_K8S_SERVICE}.${namespace}.svc.cluster.local`) + } + } + return Array.from(hosts) +} + +function routerLocalCertificateHosts (fogData, options) { + const hosts = buildRouterLocalCertificateHostList(fogData, options) + return hosts.join(',') || 'localhost' +} + +function buildNatsServerCertificateHostList (fog) { + const hosts = [fog.host, fog.ipAddress, fog.ipAddressExternal].filter(Boolean) + if (hosts.length === 0) { + hosts.push('localhost') + } + return hosts +} + +function buildNatsMqttCertificateHostList (fog) { + return [...new Set([...buildNatsServerCertificateHostList(fog), Constants.NATS_BRIDGE_DNS_SAN])] +} + +module.exports = { + getControllerNamespace, + buildRouterLocalCertificateHostList, + routerLocalCertificateHosts, + buildNatsServerCertificateHostList, + buildNatsMqttCertificateHostList +} diff --git a/src/services/iofog-service.js b/src/services/iofog-service.js index a5bc918a..da0b8688 100644 --- a/src/services/iofog-service.js +++ b/src/services/iofog-service.js @@ -44,9 +44,15 @@ const { ensureSystemApplication, getLegacySystemAppName, getSystemAppName, - getSystemMicroserviceName + getSystemMicroserviceName, + slugifyName } = require('../helpers/system-naming') const Constants = require('../helpers/constants') +const { + routerLocalCertificateHosts, + buildNatsServerCertificateHostList, + buildNatsMqttCertificateHostList +} = require('../helpers/cert-dns-sans') const Op = require('sequelize').Op const lget = require('lodash/get') const CertificateService = require('./certificate-service') @@ -58,10 +64,14 @@ const vaultManager = require('../vault/vault-manager') const SecretHelper = require('../helpers/secret-helper') const FogPublicKeyManager = require('../data/managers/iofog-public-key-manager') -const SITE_CA_CERT = 'router-site-ca' -const DEFAULT_ROUTER_LOCAL_CA = 'default-router-local-ca' +const SITE_CA_CERT = Constants.ROUTER_SITE_CA +const DEFAULT_ROUTER_LOCAL_CA = Constants.DEFAULT_ROUTER_LOCAL_CA +const NATS_SITE_CA = Constants.NATS_SITE_CA +const DEFAULT_NATS_LOCAL_CA = Constants.DEFAULT_NATS_LOCAL_CA const SERVICE_ANNOTATION_TAG = 'service.datasance.com/tag' +const _fogToken = (fog) => slugifyName((fog && fog.name) || (fog && fog.uuid) || 'fog') + function _resolveArchId (fogData) { if (fogData.archId !== undefined) return fogData.archId return undefined @@ -72,18 +82,10 @@ async function checkKubernetesEnvironment () { return controlPlane && controlPlane.toLowerCase() === 'kubernetes' } -async function getLocalCertificateHosts (fogData) { - const hosts = new Set() - const defaultHost = ['localhost', '127.0.0.1', 'host.docker.internal', 'host.containers.internal', 'iofog', 'service.local'] - // Add default hosts individually - defaultHost.forEach(host => hosts.add(host)) - if (fogData.host) hosts.add(fogData.host) - if (fogData.ipAddress) hosts.add(fogData.ipAddress) - if (fogData.ipAddressExternal) hosts.add(fogData.ipAddressExternal) - // if (isKubernetes) { - // return `router-local,router-local.${namespace},router-local.${namespace}.svc.cluster.local,127.0.0.1,localhost,host.docker.internal,host.containers.internal` - // } - return Array.from(hosts).join(',') || 'localhost' +async function getLocalCertificateHosts (fogData, uuid, transaction) { + const defaultRouter = await RouterManager.findOne({ isDefault: true }, transaction) + const isDefaultRouter = !!(defaultRouter && defaultRouter.iofogUuid === uuid) + return routerLocalCertificateHosts(fogData, { isDefaultRouter }) } async function getSiteCertificateHosts (fogData) { @@ -116,12 +118,52 @@ async function getSiteCertificateHosts (fogData) { return Array.from(hosts).join(',') || 'localhost' } -async function _handleRouterCertificates (fogData, uuid, isRouterModeChanged, transaction) { - logger.debug('Starting _handleRouterCertificates for fog: ' + JSON.stringify({ uuid: uuid, host: fogData.host })) +async function _recreateCertificateIfExists (name, subject, hosts, ca, transaction) { + try { + const existingCert = await CertificateService.getCertificateEndpoint(name, transaction) + if (!existingCert) { + return + } + await CertificateService.deleteCertificateEndpoint(name, transaction) + await CertificateService.createCertificateEndpoint({ + name, + subject: `${subject}`, + hosts, + ca + }, transaction) + } catch (err) { + if (err.name === 'NotFoundError') { + return + } + throw err + } +} - // Check if we're in Kubernetes environment - const isKubernetes = await checkKubernetesEnvironment() - // const namespace = isKubernetes ? process.env.CONTROLLER_NAMESPACE : null +async function _reconcileNatsCertificatesOnHostChange (fog, transaction) { + const fogToken = _fogToken(fog) + const serverCertName = `nats-server-${fogToken}` + const mqttCertName = `nats-mqtt-server-${fogToken}` + const serverHosts = buildNatsServerCertificateHostList(fog).join(',') + const mqttHosts = buildNatsMqttCertificateHostList(fog).join(',') + + await _recreateCertificateIfExists( + serverCertName, + serverCertName, + serverHosts, + { type: 'direct', secretName: NATS_SITE_CA }, + transaction + ) + await _recreateCertificateIfExists( + mqttCertName, + mqttCertName, + mqttHosts, + { type: 'direct', secretName: DEFAULT_NATS_LOCAL_CA }, + transaction + ) +} + +async function _handleRouterCertificates (fogData, uuid, shouldRecreateCerts, transaction) { + logger.debug('Starting _handleRouterCertificates for fog: ' + JSON.stringify({ uuid: uuid, host: fogData.host })) // Helper to check CA existence async function ensureCA (name, subject) { @@ -209,25 +251,16 @@ async function _handleRouterCertificates (fogData, uuid, isRouterModeChanged, tr // If routerMode is 'none', only ensure DEFAULT_ROUTER_LOCAL_CA and its signed certificate if (fogData.routerMode === 'none') { logger.debug('Router mode is none, ensuring DEFAULT_ROUTER_LOCAL_CA exists') - if (isKubernetes) { - await ensureCA(DEFAULT_ROUTER_LOCAL_CA, DEFAULT_ROUTER_LOCAL_CA) - } + await ensureCA(DEFAULT_ROUTER_LOCAL_CA, DEFAULT_ROUTER_LOCAL_CA) logger.debug('Ensuring local-agent certificate signed by DEFAULT_ROUTER_LOCAL_CA') - const localHosts = await getLocalCertificateHosts(fogData) - let defaultRouterLocalCA - if (isKubernetes) { - defaultRouterLocalCA = DEFAULT_ROUTER_LOCAL_CA - } else { - const defaultRouter = await RouterManager.findOne({ isDefault: true }, transaction) - defaultRouterLocalCA = `${defaultRouter.iofogUuid}-local-ca` - } + const localHosts = await getLocalCertificateHosts(fogData, uuid, transaction) await ensureCert( `router-local-agent-${fogData.name}`, `${uuid}`, localHosts, - { type: 'direct', secretName: defaultRouterLocalCA }, - isRouterModeChanged + { type: 'direct', secretName: DEFAULT_ROUTER_LOCAL_CA }, + shouldRecreateCerts ) logger.debug('Successfully completed _handleRouterCertificates for routerMode none') return @@ -242,22 +275,21 @@ async function _handleRouterCertificates (fogData, uuid, isRouterModeChanged, tr `${uuid}`, siteHosts, { type: 'direct', secretName: SITE_CA_CERT }, - false + shouldRecreateCerts ) - // Always ensure local-ca exists - logger.debug('Ensuring local-ca exists') - await ensureCA(`router-local-ca-${fogData.name}`, `${uuid}`) + logger.debug('Ensuring DEFAULT_ROUTER_LOCAL_CA exists') + await ensureCA(DEFAULT_ROUTER_LOCAL_CA, DEFAULT_ROUTER_LOCAL_CA) // Always ensure local-server cert exists logger.debug('Ensuring local-server certificate exists') - const localHosts = await getLocalCertificateHosts(fogData) + const localHosts = await getLocalCertificateHosts(fogData, uuid, transaction) await ensureCert( `router-local-server-${fogData.name}`, `${uuid}`, localHosts, - { type: 'direct', secretName: `router-local-ca-${fogData.name}` }, - isRouterModeChanged + { type: 'direct', secretName: DEFAULT_ROUTER_LOCAL_CA }, + shouldRecreateCerts ) // Always ensure local-agent cert exists @@ -266,8 +298,8 @@ async function _handleRouterCertificates (fogData, uuid, isRouterModeChanged, tr `router-local-agent-${fogData.name}`, `${uuid}`, localHosts, - { type: 'direct', secretName: `router-local-ca-${fogData.name}` }, - isRouterModeChanged + { type: 'direct', secretName: DEFAULT_ROUTER_LOCAL_CA }, + shouldRecreateCerts ) logger.debug('Successfully completed _handleRouterCertificates') @@ -596,6 +628,8 @@ async function updateFogEndPoint (fogData, isCLI, transaction) { isRouterModeChanged = true } } + const isHostChanged = !!(updateFogData.host && updateFogData.host !== oldFog.host) + const shouldRecreateCerts = isRouterModeChanged || isHostChanged await FogManager.update(queryFogData, updateFogData, transaction) await ChangeTrackingService.update(fogData.uuid, ChangeTrackingService.events.config, transaction) @@ -622,12 +656,18 @@ async function updateFogEndPoint (fogData, isCLI, transaction) { try { // --- Begin orchestration logic --- const fog = await FogManager.findOne({ uuid: fogData.uuid }, transaction) - await _handleRouterCertificates({ ...fogData, name: fog.name }, fog.uuid, isRouterModeChanged, transaction) + await _handleRouterCertificates({ ...fogData, name: fog.name }, fog.uuid, shouldRecreateCerts, transaction) + if (shouldRecreateCerts) { + await ChangeTrackingService.update(fogData.uuid, ChangeTrackingService.events.volumeMounts, transaction) + } if (natsConfig.mode === 'none') { await NatsService.cleanupNatsForFog(fog, transaction) await _deleteNatsMicroserviceByFog(fogData, transaction) await ChangeTrackingService.update(fogData.uuid, ChangeTrackingService.events.microserviceList, transaction) } else { + if (isHostChanged) { + await _reconcileNatsCertificatesOnHostChange(fog, transaction) + } await NatsService.ensureNatsForFog(fog, natsConfig, transaction) } diff --git a/src/services/nats-service.js b/src/services/nats-service.js index 92b67745..2ddb848e 100644 --- a/src/services/nats-service.js +++ b/src/services/nats-service.js @@ -46,11 +46,16 @@ const config = require('../config') const Constants = require('../helpers/constants') const { ensureSystemApplication, getSystemMicroserviceName, slugifyName } = require('../helpers/system-naming') const TransactionDecorator = require('../decorators/transaction-decorator') +const { + buildNatsServerCertificateHostList, + buildNatsMqttCertificateHostList +} = require('../helpers/cert-dns-sans') const logger = require('../logger') const K8sClient = require('../utils/k8s-client') const { Op } = require('sequelize') -const NATS_SITE_CA = 'nats-site-ca' +const NATS_SITE_CA = Constants.NATS_SITE_CA +const DEFAULT_NATS_LOCAL_CA = Constants.DEFAULT_NATS_LOCAL_CA const NATS_CONFIG_DIR = '/etc/nats/config' const NATS_JWT_DIR = '/home/runner/nats/jwt' const NATS_JWT_MOUNT_DIR = '/tmp/nats/jwt' @@ -272,17 +277,15 @@ async function _ensureNatsCertificates (fog, transaction) { } await ensureCA(NATS_SITE_CA, NATS_SITE_CA) - await ensureCA(natsLocalCaName(fog), natsLocalCaName(fog)) + await ensureCA(DEFAULT_NATS_LOCAL_CA, DEFAULT_NATS_LOCAL_CA) - const hosts = [fog.host, fog.ipAddress, fog.ipAddressExternal].filter(Boolean) - if (hosts.length === 0) { - hosts.push('localhost') - } + const serverHosts = buildNatsServerCertificateHostList(fog) + const mqttHosts = buildNatsMqttCertificateHostList(fog) const serverCertName = natsServerCertName(fog) const mqttCertName = natsLocalMQTTCertName(fog) - await ensureCert(serverCertName, serverCertName, hosts, NATS_SITE_CA) - await ensureCert(mqttCertName, mqttCertName, hosts, natsLocalCaName(fog)) + await ensureCert(serverCertName, serverCertName, serverHosts, NATS_SITE_CA) + await ensureCert(mqttCertName, mqttCertName, mqttHosts, DEFAULT_NATS_LOCAL_CA) return { serverCertName, From 2934c5f8856e8c624515b668ba18f746dc79c067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 06:50:32 +0300 Subject: [PATCH 24/75] Resolve TCP bridge connector hosts for Edgelet bridge DNS. Use appName.microserviceName for non-hostNetwork microservices and edgelet.default.bridge.local for hostNetwork microservices and agent services. Remove legacy iofog and iofog_{uuid} connector host values. --- src/services/services-service.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/services/services-service.js b/src/services/services-service.js index da4bc45e..a46b537e 100644 --- a/src/services/services-service.js +++ b/src/services/services-service.js @@ -35,6 +35,7 @@ const { const K8S_ROUTER_CONFIG_MAP = 'iofog-router' const SERVICE_ANNOTATION_TAG = 'service.datasance.com/tag' +const EDGELET_BRIDGE_CONNECTOR_HOST = 'edgelet.default.bridge.local' // Map service tags to string array // Return plain JS object @@ -271,15 +272,22 @@ async function defineBridgePort (serviceConfig, transaction) { // Helper function to determine host based on service type async function _determineConnectorHost (serviceConfig, transaction) { switch (serviceConfig.type.toLowerCase()) { - case 'microservice': + case 'microservice': { const microservice = await MicroserviceManager.findOne({ uuid: serviceConfig.resource }, transaction) + if (!microservice) { + throw new Errors.NotFoundError(`Microservice not found: ${serviceConfig.resource}`) + } if (microservice.hostNetworkMode) { - return 'iofog' - } else { - return `iofog_${serviceConfig.resource}` + return EDGELET_BRIDGE_CONNECTOR_HOST + } + const application = await ApplicationManager.findOne({ id: microservice.applicationId }, transaction) + if (!application) { + throw new Errors.NotFoundError(`Application not found for microservice: ${serviceConfig.resource}`) } - case 'agent': // TODO: find agent extract router config mode from agent router mode. - return 'iofog' + return `${application.name}.${microservice.name}` + } + case 'agent': + return EDGELET_BRIDGE_CONNECTOR_HOST case 'k8s': case 'external': return serviceConfig.resource @@ -1333,5 +1341,6 @@ module.exports = { deleteServiceEndpoint: TransactionDecorator.generateTransaction(deleteServiceEndpoint), getServicesListEndpoint: TransactionDecorator.generateTransaction(getServicesListEndpoint), getServiceEndpoint: TransactionDecorator.generateTransaction(getServiceEndpoint), - moveMicroserviceTcpBridgeToNewFog: TransactionDecorator.generateTransaction(moveMicroserviceTcpBridgeToNewFog) + moveMicroserviceTcpBridgeToNewFog: TransactionDecorator.generateTransaction(moveMicroserviceTcpBridgeToNewFog), + _determineConnectorHost } From 469c609cb424b752b1bcdffc92e6165f02d94122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 06:50:39 +0300 Subject: [PATCH 25/75] Add unit tests for certificate DNS SANs, central PKI, and connector hosts. Cover bridge SAN host lists, greenfield central CA provisioning gates, and _determineConnectorHost resolution for microservice and agent services. --- test/src/helpers/cert-dns-sans.test.js | 85 +++++++++++ .../src/services/iofog-greenfield-pki.test.js | 91 ++++++++++++ .../services/services-connector-host.test.js | 139 ++++++++++++++++++ 3 files changed, 315 insertions(+) create mode 100644 test/src/helpers/cert-dns-sans.test.js create mode 100644 test/src/services/iofog-greenfield-pki.test.js create mode 100644 test/src/services/services-connector-host.test.js diff --git a/test/src/helpers/cert-dns-sans.test.js b/test/src/helpers/cert-dns-sans.test.js new file mode 100644 index 00000000..233bdd8d --- /dev/null +++ b/test/src/helpers/cert-dns-sans.test.js @@ -0,0 +1,85 @@ +const { expect } = require('chai') + +const Constants = require('../../../src/helpers/constants') +const { + buildRouterLocalCertificateHostList, + routerLocalCertificateHosts, + buildNatsServerCertificateHostList, + buildNatsMqttCertificateHostList +} = require('../../../src/helpers/cert-dns-sans') + +describe('cert-dns-sans', () => { + const originalNamespace = process.env.CONTROLLER_NAMESPACE + + afterEach(() => { + if (originalNamespace === undefined) { + delete process.env.CONTROLLER_NAMESPACE + } else { + process.env.CONTROLLER_NAMESPACE = originalNamespace + } + }) + + describe('buildRouterLocalCertificateHostList()', () => { + const fogData = { + host: '10.0.0.5', + ipAddress: '192.168.1.10', + ipAddressExternal: '203.0.113.9' + } + + it('always includes router bridge DNS SAN', () => { + const hosts = buildRouterLocalCertificateHostList(fogData) + + expect(hosts).to.include(Constants.ROUTER_BRIDGE_DNS_SAN) + expect(hosts).to.include.members(['localhost', '127.0.0.1', fogData.host, fogData.ipAddress, fogData.ipAddressExternal]) + }) + + it('adds cluster.local SAN for default router when namespace is set', () => { + process.env.CONTROLLER_NAMESPACE = 'edge-prod' + + const hosts = buildRouterLocalCertificateHostList(fogData, { isDefaultRouter: true }) + + expect(hosts).to.include('router.edge-prod.svc.cluster.local') + }) + + it('does not add cluster.local SAN for non-default router', () => { + process.env.CONTROLLER_NAMESPACE = 'edge-prod' + + const hosts = buildRouterLocalCertificateHostList(fogData, { isDefaultRouter: false }) + + expect(hosts).to.not.include('router.edge-prod.svc.cluster.local') + }) + + it('joins hosts for routerLocalCertificateHosts()', () => { + const hosts = routerLocalCertificateHosts(fogData, { isDefaultRouter: false }) + + expect(hosts).to.be.a('string') + expect(hosts.split(',')).to.include(Constants.ROUTER_BRIDGE_DNS_SAN) + }) + }) + + describe('buildNatsMqttCertificateHostList()', () => { + const fog = { + host: '10.0.0.8', + ipAddress: '192.168.1.20' + } + + it('includes fog network hosts for server cert only', () => { + const hosts = buildNatsServerCertificateHostList(fog) + + expect(hosts).to.deep.equal([fog.host, fog.ipAddress]) + expect(hosts).to.not.include(Constants.NATS_BRIDGE_DNS_SAN) + }) + + it('adds NATS bridge DNS SAN for MQTT cert', () => { + const hosts = buildNatsMqttCertificateHostList(fog) + + expect(hosts).to.include.members([fog.host, fog.ipAddress, Constants.NATS_BRIDGE_DNS_SAN]) + }) + + it('falls back to localhost when fog has no network fields', () => { + const hosts = buildNatsMqttCertificateHostList({}) + + expect(hosts).to.deep.equal(['localhost', Constants.NATS_BRIDGE_DNS_SAN]) + }) + }) +}) diff --git a/test/src/services/iofog-greenfield-pki.test.js b/test/src/services/iofog-greenfield-pki.test.js new file mode 100644 index 00000000..709ab72e --- /dev/null +++ b/test/src/services/iofog-greenfield-pki.test.js @@ -0,0 +1,91 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const fs = require('fs') +const path = require('path') + +const Constants = require('../../../src/helpers/constants') +const ioFogService = require('../../../src/services/iofog-service') +const CertificateService = require('../../../src/services/certificate-service') +const RouterManager = require('../../../src/data/managers/router-manager') + +describe('iofog greenfield PKI (central local CAs)', () => { + def('sandbox', () => sinon.createSandbox()) + def('transaction', () => ({})) + + afterEach(() => $sandbox.restore()) + + describe('provision source gates', () => { + it('does not create per-agent router-local-ca secrets in iofog-service (delete cleanup only)', () => { + const source = fs.readFileSync( + path.join(__dirname, '../../../src/services/iofog-service.js'), + 'utf8' + ) + const matches = source.match(/router-local-ca-\$\{/g) || [] + expect(matches).to.have.lengthOf(1) + expect(source).to.include('_processDeleteCommand') + expect(source).to.not.match(/ensureCA\(\s*`router-local-ca-\$\{/) + expect(source).to.not.match(/createCAEndpoint\([^)]*router-local-ca-\$\{/) + }) + + it('does not create per-agent nats-local-ca in _ensureNatsCertificates', () => { + const source = fs.readFileSync( + path.join(__dirname, '../../../src/services/nats-service.js'), + 'utf8' + ) + const certBlock = source.slice( + source.indexOf('async function _ensureNatsCertificates'), + source.indexOf('async function _buildJwtBundle') + ) + expect(certBlock).to.include('DEFAULT_NATS_LOCAL_CA') + expect(certBlock).to.not.include('natsLocalCaName(fog)') + expect(certBlock).to.not.match(/ensureCA\([^)]*nats-local-ca/) + }) + }) + + describe('_handleRouterCertificates()', () => { + const uuid = 'fog-uuid-abc' + const fogData = { + name: 'edge-node-1', + host: '10.0.0.5', + ipAddress: '192.168.1.10', + routerMode: 'client' + } + + def('subject', () => ioFogService._handleRouterCertificates(fogData, uuid, false, $transaction)) + + beforeEach(() => { + $sandbox.stub(RouterManager, 'findOne').resolves({ iofogUuid: 'other-fog' }) + $sandbox.stub(CertificateService, 'getCAEndpoint').rejects({ name: 'NotFoundError' }) + $sandbox.stub(CertificateService, 'createCAEndpoint').resolves() + $sandbox.stub(CertificateService, 'getCertificateEndpoint').rejects({ name: 'NotFoundError' }) + $sandbox.stub(CertificateService, 'createCertificateEndpoint').resolves() + }) + + it('ensures central router local CA and signs certs with it', async () => { + await $subject + + const createCaCalls = CertificateService.createCAEndpoint.getCalls() + const caNames = createCaCalls.map((call) => call.args[0].name) + expect(caNames).to.include(Constants.ROUTER_SITE_CA) + expect(caNames).to.include(Constants.DEFAULT_ROUTER_LOCAL_CA) + expect(caNames).to.not.include(`router-local-ca-${fogData.name}`) + + const createCertCalls = CertificateService.createCertificateEndpoint.getCalls() + const signingCaNames = createCertCalls.map((call) => call.args[0].ca.secretName) + expect(signingCaNames).to.include(Constants.DEFAULT_ROUTER_LOCAL_CA) + expect(signingCaNames.every((name) => name !== `router-local-ca-${fogData.name}`)).to.equal(true) + }) + + it('creates router-local-server and router-local-agent certs (not per-agent CA)', async () => { + await $subject + + const certNames = CertificateService.createCertificateEndpoint.getCalls().map((call) => call.args[0].name) + expect(certNames).to.include.members([ + `router-local-server-${fogData.name}`, + `router-local-agent-${fogData.name}`, + `router-site-server-${fogData.name}` + ]) + expect(certNames).to.not.include(`router-local-ca-${fogData.name}`) + }) + }) +}) diff --git a/test/src/services/services-connector-host.test.js b/test/src/services/services-connector-host.test.js new file mode 100644 index 00000000..e56c9d60 --- /dev/null +++ b/test/src/services/services-connector-host.test.js @@ -0,0 +1,139 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const ServicesService = require('../../../src/services/services-service') +const MicroserviceManager = require('../../../src/data/managers/microservice-manager') +const ApplicationManager = require('../../../src/data/managers/application-manager') +const Errors = require('../../../src/helpers/errors') + +const EDGELET_BRIDGE_CONNECTOR_HOST = 'edgelet.default.bridge.local' + +describe('services-service connector host', () => { + def('sandbox', () => sinon.createSandbox()) + def('transaction', () => ({})) + + afterEach(() => $sandbox.restore()) + + describe('_determineConnectorHost()', () => { + def('subject', () => ServicesService._determineConnectorHost($serviceConfig, $transaction)) + + describe('microservice (non-hostNetwork)', () => { + def('serviceConfig', () => ({ + type: 'microservice', + resource: 'ms-uuid-1', + name: 'my-svc', + targetPort: 8080 + })) + def('microservice', () => ({ + uuid: 'ms-uuid-1', + name: 'worker', + applicationId: 42, + hostNetworkMode: false, + iofogUuid: 'fog-1' + })) + def('application', () => ({ id: 42, name: 'myapp' })) + + beforeEach(() => { + $sandbox.stub(MicroserviceManager, 'findOne').resolves($microservice) + $sandbox.stub(ApplicationManager, 'findOne').resolves($application) + }) + + it('returns appName.microserviceName', async () => { + const host = await $subject + expect(host).to.equal('myapp.worker') + }) + }) + + describe('microservice (hostNetwork)', () => { + def('serviceConfig', () => ({ + type: 'microservice', + resource: 'ms-uuid-2', + name: 'host-svc', + targetPort: 9090 + })) + def('microservice', () => ({ + uuid: 'ms-uuid-2', + name: 'daemon', + applicationId: 7, + hostNetworkMode: true, + iofogUuid: 'fog-2' + })) + + beforeEach(() => { + $sandbox.stub(MicroserviceManager, 'findOne').resolves($microservice) + }) + + it('returns edgelet bridge connector host', async () => { + const host = await $subject + expect(host).to.equal(EDGELET_BRIDGE_CONNECTOR_HOST) + }) + }) + + describe('agent service', () => { + def('serviceConfig', () => ({ + type: 'agent', + resource: 'fog-agent-1', + name: 'agent-svc', + targetPort: 22 + })) + + it('returns edgelet bridge connector host', async () => { + const host = await $subject + expect(host).to.equal(EDGELET_BRIDGE_CONNECTOR_HOST) + }) + }) + + describe('k8s / external', () => { + it('passes through resource for k8s', async () => { + const host = await ServicesService._determineConnectorHost({ + type: 'k8s', + resource: 'k8s-svc.default.svc.cluster.local', + name: 'k8s-svc', + targetPort: 443 + }, $transaction) + expect(host).to.equal('k8s-svc.default.svc.cluster.local') + }) + + it('passes through resource for external', async () => { + const host = await ServicesService._determineConnectorHost({ + type: 'external', + resource: 'external.example.com', + name: 'ext-svc', + targetPort: 443 + }, $transaction) + expect(host).to.equal('external.example.com') + }) + }) + + describe('errors', () => { + def('serviceConfig', () => ({ + type: 'microservice', + resource: 'missing-ms', + name: 'bad-svc', + targetPort: 80 + })) + + it('throws NotFoundError when microservice is missing', async () => { + $sandbox.stub(MicroserviceManager, 'findOne').resolves(null) + await expect($subject).to.be.rejectedWith( + Errors.NotFoundError, + /Microservice not found/ + ) + }) + + it('throws NotFoundError when application is missing', async () => { + $sandbox.stub(MicroserviceManager, 'findOne').resolves({ + uuid: 'ms-uuid', + name: 'worker', + applicationId: 99, + hostNetworkMode: false + }) + $sandbox.stub(ApplicationManager, 'findOne').resolves(null) + await expect($subject).to.be.rejectedWith( + Errors.NotFoundError, + /Application not found/ + ) + }) + }) + }) +}) From 8426a4ea5d1e0f1e059458932295b7c07e5760ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 06:50:47 +0300 Subject: [PATCH 26/75] Remove linkedEdgeResources from ChangeTracking model. Drop the deprecated linked_edge_resources flag from the Sequelize model. --- src/data/models/changetracking.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/data/models/changetracking.js b/src/data/models/changetracking.js index 9b071f95..710d17d4 100644 --- a/src/data/models/changetracking.js +++ b/src/data/models/changetracking.js @@ -71,11 +71,6 @@ module.exports = (sequelize, DataTypes) => { field: 'prune', defaultValue: false }, - linkedEdgeResources: { - type: DataTypes.BOOLEAN, - field: 'linked_edge_resources', - defaultValue: false - }, volumeMounts: { type: DataTypes.BOOLEAN, field: 'volume_mounts', From 8f6b71ae7955d0741234d5a9eca88d60bae043bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 13:21:03 +0300 Subject: [PATCH 27/75] Add remote router AMQP resolve fallback and connect retry. Remote control planes resolve hosts in order: bridge DNS, database host, then cluster.local. Kubernetes keeps a single cluster.local host. Connect failures log host and error, then try the next candidate. --- src/services/router-connection-service.js | 247 ++++++++++++++-------- 1 file changed, 157 insertions(+), 90 deletions(-) diff --git a/src/services/router-connection-service.js b/src/services/router-connection-service.js index fbc12275..8428d80c 100644 --- a/src/services/router-connection-service.js +++ b/src/services/router-connection-service.js @@ -1,6 +1,7 @@ const rhea = require('rhea') const config = require('../config') const logger = require('../logger') +const Constants = require('../helpers/constants') const RouterManager = require('../data/managers/router-manager') const FogManager = require('../data/managers/iofog-manager') const CertificateService = require('./certificate-service') @@ -42,59 +43,62 @@ class RouterConnectionService { async _createConnection () { try { - const options = await this._buildConnectionOptions() - return await new Promise((resolve, reject) => { - const connection = this.container.connect(options) + logger.debug({ msg: '[AMQP] Preparing router connection options' }) - const cleanupPromise = () => { - this.connection = null - this.connectionPromise = null - } + const { hosts, port } = await this._resolveRouterEndpoint() + logger.debug({ msg: '[AMQP] Router endpoint resolved', hosts, port }) + const certBundle = await this._ensureControllerCertificate() - connection.once('connection_open', () => { - logger.info('[AMQP] Router connection established') - this.connection = connection - this.connectionPromise = null - connection.on('connection_error', (context) => { - logger.error({ - err: context.error, - transport: 'amqp', - msg: '[AMQP] Connection error event' - }) - }) - connection.on('connection_close', () => { - logger.warn('[AMQP] Router connection closed') - cleanupPromise() + let lastError = null + for (let attempt = 0; attempt < hosts.length; attempt++) { + const host = hosts[attempt] + const options = this._buildConnectOptions(host, port, certBundle) + try { + const connection = await this._connectToHost(host, port, options) + this.connectionOptions = { + transport: 'tls', + host, + hostname: host, + port, + rejectUnauthorized: true, + idle_time_out: 300000, + reconnect: true, + reconnect_limit: 100, + username: '', + password: '', + container_id: 'controller-exec-session-client' + } + this.cachedCertificate = certBundle + logger.info({ + msg: '[AMQP] Router connection established', + host, + port, + attempt: attempt + 1, + totalAttempts: hosts.length }) - connection.on('disconnected', (context) => { - logger.warn('[AMQP] Router connection disconnected', { - error: context.error ? context.error.message : 'unknown' - }) - cleanupPromise() + return connection + } catch (error) { + lastError = error + logger.warn({ + msg: '[AMQP] Router connect attempt failed', + host, + port, + attempt: attempt + 1, + totalAttempts: hosts.length, + err: error.message || String(error) }) - resolve(connection) - }) - - connection.once('connection_close', (context) => { - logger.error({ - err: context.error, - transport: 'amqp', - msg: '[AMQP] Unable to open router connection (closed before open)' - }) - cleanupPromise() - reject(new Error('Router connection closed before opening')) - }) + } + } - connection.once('disconnected', (context) => { - logger.error({ - err: context.error, - transport: 'amqp', - msg: '[AMQP] Unable to connect to router' - }) - cleanupPromise() - reject(context.error || new Error('Router disconnected during connect')) - }) + const aggregateError = lastError || new Error('No router hosts available for connection') + logger.error({ + err: aggregateError, + transport: 'amqp', + msg: '[AMQP] Unable to connect to router after all fallback hosts', + hosts, + port }) + throw aggregateError } catch (error) { this.connectionPromise = null logger.error('[AMQP] Failed to create router connection', { @@ -105,75 +109,97 @@ class RouterConnectionService { } } - async _buildConnectionOptions () { - if (this.connectionOptions && this.cachedCertificate) { - return { - ...this.connectionOptions, - cert: this.cachedCertificate.cert, - key: this.cachedCertificate.key, - ca: [this.cachedCertificate.ca] - } - } - - logger.debug({ msg: '[AMQP] Preparing router connection options' }) - - const { host, port } = await this._resolveRouterEndpoint() - logger.debug({ msg: '[AMQP] Router endpoint resolved', host, port }) - const certBundle = await this._ensureControllerCertificate() - - this.connectionOptions = { + _buildConnectOptions (host, port, certBundle) { + logger.debug({ msg: '[AMQP] Router connection options built', host, port }) + return { transport: 'tls', host, hostname: host, port, rejectUnauthorized: true, idle_time_out: 300000, - reconnect: true, - reconnect_limit: 100, + reconnect: false, username: '', password: '', - container_id: 'controller-exec-session-client' - } - this.cachedCertificate = certBundle - - logger.debug({ msg: '[AMQP] Router connection options built', host, port }) - - return { - ...this.connectionOptions, + container_id: 'controller-exec-session-client', cert: certBundle.cert, key: certBundle.key, ca: [certBundle.ca] } } + _connectToHost (host, port, options) { + return new Promise((resolve, reject) => { + const connection = this.container.connect(options) + let settled = false + + const settle = (handler) => (context) => { + if (settled) return + settled = true + handler(context) + } + + const cleanupPromise = () => { + this.connection = null + this.connectionPromise = null + } + + connection.once('connection_open', settle(() => { + this.connection = connection + this.connectionPromise = null + connection.on('connection_error', (context) => { + logger.error({ + err: context.error, + transport: 'amqp', + msg: '[AMQP] Connection error event', + host, + port + }) + }) + connection.on('connection_close', () => { + logger.warn('[AMQP] Router connection closed', { host, port }) + cleanupPromise() + }) + connection.on('disconnected', (context) => { + logger.warn('[AMQP] Router connection disconnected', { + host, + port, + error: context.error ? context.error.message : 'unknown' + }) + cleanupPromise() + }) + resolve(connection) + })) + + connection.once('connection_close', settle((context) => { + reject(context.error || new Error('Router connection closed before opening')) + })) + + connection.once('disconnected', settle((context) => { + reject(context.error || new Error('Router disconnected during connect')) + })) + }) + } + async _resolveRouterEndpoint () { logger.debug({ msg: '[AMQP] Resolving default router endpoint' }) try { const router = await this._getDefaultRouterRecord() const port = router.messagingPort || AMQP_DEFAULT_PORT - let host = router.host && router.host.trim().length > 0 ? router.host.trim() : '' - - if (this._isKubernetes()) { - const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace') - if (namespace && namespace.trim().length > 0) { - host = `${DEFAULT_ROUTER_SERVICE}.${namespace}.svc.cluster.local` - } else if (!host) { - host = DEFAULT_ROUTER_SERVICE - } - } else { - if (!host) { - host = 'localhost' - } - } + const hosts = this._buildRouterHostList(router) + const host = hosts[0] logger.debug({ msg: '[AMQP] Default router resolved', routerHost: router.host, - computedHost: host, + hosts, + host, port, - routerUuid: router.iofogUuid + routerUuid: router.iofogUuid, + controlPlane: this._isKubernetes() ? 'kubernetes' : 'remote' }) return { host, + hosts, port, routerUuid: router.iofogUuid } @@ -183,6 +209,47 @@ class RouterConnectionService { } } + _buildRouterHostList (router) { + if (this._isKubernetes()) { + return [this._kubernetesRouterHost(router)] + } + return this._remoteRouterHosts(router) + } + + _kubernetesRouterHost (router) { + const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace') + if (namespace && namespace.trim().length > 0) { + return `${DEFAULT_ROUTER_SERVICE}.${namespace.trim()}.svc.cluster.local` + } + const dbHost = router.host && router.host.trim().length > 0 ? router.host.trim() : '' + return dbHost || DEFAULT_ROUTER_SERVICE + } + + _remoteRouterHosts (router) { + const hosts = [] + const seen = new Set() + const addHost = (candidate) => { + if (!candidate) return + const trimmed = String(candidate).trim() + if (trimmed.length === 0 || seen.has(trimmed)) return + seen.add(trimmed) + hosts.push(trimmed) + } + + addHost(Constants.ROUTER_BRIDGE_DNS_SAN) + + if (router.host) { + addHost(router.host) + } + + const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace') + if (namespace && namespace.trim().length > 0) { + addHost(`${DEFAULT_ROUTER_SERVICE}.${namespace.trim()}.svc.cluster.local`) + } + + return hosts + } + async _getDefaultRouterRecord () { if (this.cachedRouterRecord) { return this.cachedRouterRecord From 5adcd70c714d28a930e0b23af121435930454ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 13:21:07 +0300 Subject: [PATCH 28/75] Add unit tests for router connection resolve and fallback connect. Cover Kubernetes single-host path, remote ordered hosts, deduplication, namespace fallbacks, and sequential connect retry with mocked endpoints. --- .../router-connection-service.test.js | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 test/src/services/router-connection-service.test.js diff --git a/test/src/services/router-connection-service.test.js b/test/src/services/router-connection-service.test.js new file mode 100644 index 00000000..1208ceaa --- /dev/null +++ b/test/src/services/router-connection-service.test.js @@ -0,0 +1,199 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const Constants = require('../../../src/helpers/constants') +const config = require('../../../src/config') +const RouterManager = require('../../../src/data/managers/router-manager') +const RouterConnectionService = require('../../../src/services/router-connection-service') + +describe('Router Connection Service', () => { + def('sandbox', () => sinon.createSandbox()) + + const defaultRouter = { + host: 'router-db.example.com', + messagingPort: 5671, + iofogUuid: 'default-router-uuid' + } + + const originalControlPlane = process.env.CONTROL_PLANE + const originalNamespace = process.env.CONTROLLER_NAMESPACE + + beforeEach(() => { + RouterConnectionService.connection = null + RouterConnectionService.connectionPromise = null + RouterConnectionService.cachedRouterRecord = null + RouterConnectionService.cachedCertificate = null + RouterConnectionService.connectionOptions = null + RouterConnectionService.certificatePromise = null + + $sandbox.stub(RouterManager, 'findOne').resolves(defaultRouter) + }) + + afterEach(() => { + $sandbox.restore() + + if (originalControlPlane === undefined) { + delete process.env.CONTROL_PLANE + } else { + process.env.CONTROL_PLANE = originalControlPlane + } + + if (originalNamespace === undefined) { + delete process.env.CONTROLLER_NAMESPACE + } else { + process.env.CONTROLLER_NAMESPACE = originalNamespace + } + }) + + describe('_resolveRouterEndpoint() — Kubernetes control plane', () => { + beforeEach(() => { + process.env.CONTROL_PLANE = 'kubernetes' + process.env.CONTROLLER_NAMESPACE = 'iofog' + }) + + it('returns a single cluster.local service host', async () => { + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.deep.equal(['router.iofog.svc.cluster.local']) + expect(result.host).to.equal('router.iofog.svc.cluster.local') + expect(result.port).to.equal(5671) + expect(result.routerUuid).to.equal(defaultRouter.iofogUuid) + }) + + it('does not include bridge DNS or DB host in the fallback list', async () => { + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.not.include(Constants.ROUTER_BRIDGE_DNS_SAN) + expect(result.hosts).to.not.include(defaultRouter.host) + }) + + it('falls back to DB host when namespace is unset', async () => { + delete process.env.CONTROLLER_NAMESPACE + $sandbox.stub(config, 'get').withArgs('app.namespace').returns('') + + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.deep.equal([defaultRouter.host]) + }) + + it('falls back to default router service name when namespace and DB host are unset', async () => { + delete process.env.CONTROLLER_NAMESPACE + $sandbox.stub(config, 'get').withArgs('app.namespace').returns('') + RouterManager.findOne.resolves({ + ...defaultRouter, + host: '' + }) + + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.deep.equal(['router']) + }) + }) + + describe('_resolveRouterEndpoint() — Remote control plane', () => { + beforeEach(() => { + process.env.CONTROL_PLANE = 'remote' + process.env.CONTROLLER_NAMESPACE = 'edge-ns' + }) + + it('returns ordered fallback hosts: bridge DNS, DB host, cluster.local', async () => { + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.deep.equal([ + Constants.ROUTER_BRIDGE_DNS_SAN, + defaultRouter.host, + 'router.edge-ns.svc.cluster.local' + ]) + expect(result.host).to.equal(Constants.ROUTER_BRIDGE_DNS_SAN) + expect(result.port).to.equal(5671) + }) + + it('deduplicates hosts when DB host matches bridge DNS', async () => { + RouterManager.findOne.resolves({ + ...defaultRouter, + host: Constants.ROUTER_BRIDGE_DNS_SAN + }) + + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.deep.equal([ + Constants.ROUTER_BRIDGE_DNS_SAN, + 'router.edge-ns.svc.cluster.local' + ]) + }) + + it('omits cluster.local host when namespace is unset', async () => { + delete process.env.CONTROLLER_NAMESPACE + $sandbox.stub(config, 'get').withArgs('app.namespace').returns('') + + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.deep.equal([ + Constants.ROUTER_BRIDGE_DNS_SAN, + defaultRouter.host + ]) + }) + + it('uses default AMQP port when router messagingPort is unset', async () => { + RouterManager.findOne.resolves({ + ...defaultRouter, + messagingPort: null + }) + + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.port).to.equal(5671) + }) + }) + + describe('_createConnection() — Remote connect fallback', () => { + const certBundle = { + cert: Buffer.from('cert'), + key: Buffer.from('key'), + ca: Buffer.from('ca') + } + + def('mockConnection', () => ({ is_open: () => true })) + + beforeEach(() => { + $sandbox.stub(RouterConnectionService, '_ensureControllerCertificate').resolves(certBundle) + }) + + it('tries hosts in order until one connects', async () => { + const hosts = [Constants.ROUTER_BRIDGE_DNS_SAN, defaultRouter.host, 'router.edge-ns.svc.cluster.local'] + $sandbox.stub(RouterConnectionService, '_resolveRouterEndpoint').resolves({ hosts, port: 5671 }) + const connectStub = $sandbox.stub(RouterConnectionService, '_connectToHost') + .onCall(0).rejects(new Error('ECONNREFUSED')) + .onCall(1).resolves($mockConnection) + + const connection = await RouterConnectionService._createConnection() + + expect(connection).to.equal($mockConnection) + expect(connectStub).to.have.been.calledTwice + expect(connectStub.firstCall.args[0]).to.equal(Constants.ROUTER_BRIDGE_DNS_SAN) + expect(connectStub.secondCall.args[0]).to.equal(defaultRouter.host) + }) + + it('throws after all hosts fail', async () => { + const hosts = [Constants.ROUTER_BRIDGE_DNS_SAN, defaultRouter.host] + const lastError = new Error('all hosts down') + $sandbox.stub(RouterConnectionService, '_resolveRouterEndpoint').resolves({ hosts, port: 5671 }) + $sandbox.stub(RouterConnectionService, '_connectToHost').rejects(lastError) + + await expect(RouterConnectionService._createConnection()).to.be.rejectedWith('all hosts down') + expect(RouterConnectionService._connectToHost).to.have.been.calledTwice + }) + + it('connects on first host for Kubernetes single-host list', async () => { + const hosts = ['router.iofog.svc.cluster.local'] + $sandbox.stub(RouterConnectionService, '_resolveRouterEndpoint').resolves({ hosts, port: 5671 }) + const connectStub = $sandbox.stub(RouterConnectionService, '_connectToHost').resolves($mockConnection) + + const connection = await RouterConnectionService._createConnection() + + expect(connection).to.equal($mockConnection) + expect(connectStub).to.have.been.calledOnce + expect(connectStub.firstCall.args[0]).to.equal('router.iofog.svc.cluster.local') + }) + }) +}) From d0da6c7ccfe41793c8517608849fdb25b1a17d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 14:42:59 +0300 Subject: [PATCH 29/75] Add agent controller microservice register endpoint and upsert service. System fogs register the ControlPlane controller workload via POST /api/v3/agent/controller/register. Upserts by client uuid, sets isController on the row, and skips service account volume injection. --- src/controllers/agent-controller.js | 8 +- src/routes/agent.js | 36 ++ src/schemas/controller-register.js | 50 ++ src/services/controller-ms-service.js | 426 ++++++++++++++++++ .../services/controller-ms-service.test.js | 234 ++++++++++ 5 files changed, 753 insertions(+), 1 deletion(-) create mode 100644 src/schemas/controller-register.js create mode 100644 src/services/controller-ms-service.js create mode 100644 test/src/services/controller-ms-service.test.js diff --git a/src/controllers/agent-controller.js b/src/controllers/agent-controller.js index ecbee291..2584ef1b 100644 --- a/src/controllers/agent-controller.js +++ b/src/controllers/agent-controller.js @@ -12,6 +12,7 @@ */ const AgentService = require('../services/agent-service') +const ControllerMsService = require('../services/controller-ms-service') const AuthDecorator = require('../decorators/authorization-decorator') const agentProvisionEndPoint = async function (req) { @@ -106,6 +107,10 @@ const getControllerCAEndPoint = async function (req, fog) { return AgentService.getControllerCA(fog) } +const registerControllerMicroserviceEndPoint = async function (req, fog) { + return ControllerMsService.registerControllerMicroservice(req.body, fog) +} + module.exports = { agentProvisionEndPoint: agentProvisionEndPoint, agentDeprovisionEndPoint: AuthDecorator.checkFogToken(agentDeprovisionEndPoint), @@ -125,5 +130,6 @@ module.exports = { resetAgentConfigChangesEndPoint: AuthDecorator.checkFogToken(resetAgentConfigChangesEndPoint), getAgentLinkedVolumeMountsEndpoint: AuthDecorator.checkFogToken(getAgentLinkedVolumeMountsEndpoint), getControllerCAEndPoint: AuthDecorator.checkFogToken(getControllerCAEndPoint), - getAgentLogSessionsEndPoint: AuthDecorator.checkFogToken(getAgentLogSessionsEndPoint) + getAgentLogSessionsEndPoint: AuthDecorator.checkFogToken(getAgentLogSessionsEndPoint), + registerControllerMicroserviceEndPoint: AuthDecorator.checkFogToken(registerControllerMicroserviceEndPoint) } diff --git a/src/routes/agent.js b/src/routes/agent.js index de9ee1f1..8fed2025 100644 --- a/src/routes/agent.js +++ b/src/routes/agent.js @@ -329,6 +329,42 @@ module.exports = [ logger.apiRes({ req: req, res: res, responseObject: responseObject }) } }, + { + method: 'post', + path: '/api/v3/agent/controller/register', + middleware: async (req, res) => { + logger.apiReq(req) + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError] + }, + { + code: constants.HTTP_CODE_FORBIDDEN, + errors: [Errors.ForbiddenError] + }, + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] + } + ] + + const registerControllerMicroserviceEndPoint = ResponseDecorator.handleErrors( + AgentController.registerControllerMicroserviceEndPoint, + successCode, + errorCodes + ) + const responseObject = await registerControllerMicroserviceEndPoint(req) + + res + .status(responseObject.code) + .send(responseObject.body) + + logger.apiRes({ req: req, res: res, responseObject: responseObject }) + } + }, { method: 'get', path: '/api/v3/agent/registries', diff --git a/src/schemas/controller-register.js b/src/schemas/controller-register.js new file mode 100644 index 00000000..000a4776 --- /dev/null +++ b/src/schemas/controller-register.js @@ -0,0 +1,50 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const controllerRegister = { + 'id': '/controllerRegister', + 'type': 'object', + 'properties': { + 'uuid': { 'type': 'string' }, + 'name': { 'type': 'string', 'enum': ['controller'] }, + 'images': { + 'type': 'array', + 'minItems': 1, + 'maxItems': 2, + 'items': { '$ref': '/image' } + }, + 'registryId': { 'type': 'integer' }, + 'ports': { + 'type': 'array', + 'items': { '$ref': '/ports' } + }, + 'volumeMappings': { + 'type': 'array', + 'items': { '$ref': '/volumeMappings' } + }, + 'env': { + 'type': 'array', + 'items': { '$ref': '/env' } + }, + 'config': { 'type': 'string' }, + 'hostNetworkMode': { 'type': 'boolean' }, + 'runtime': { 'type': 'string' } + }, + 'required': ['uuid', 'images', 'registryId'], + 'additionalProperties': false +} + +module.exports = { + mainSchemas: [controllerRegister], + innerSchemas: [] +} diff --git a/src/services/controller-ms-service.js b/src/services/controller-ms-service.js new file mode 100644 index 00000000..46a7f588 --- /dev/null +++ b/src/services/controller-ms-service.js @@ -0,0 +1,426 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const TransactionDecorator = require('../decorators/transaction-decorator') +const Validator = require('../schemas/index') +const Errors = require('../helpers/errors') +const AppHelper = require('../helpers/app-helper') +const ErrorMessages = require('../helpers/error-messages') +const { ensureSystemApplication } = require('../helpers/system-naming') +const MicroserviceManager = require('../data/managers/microservice-manager') +const MicroserviceStatusManager = require('../data/managers/microservice-status-manager') +const MicroserviceExecStatusManager = require('../data/managers/microservice-exec-status-manager') +const CatalogItemImageManager = require('../data/managers/catalog-item-image-manager') +const MicroserviceEnvManager = require('../data/managers/microservice-env-manager') +const RegistryManager = require('../data/managers/registry-manager') +const VolumeMappingManager = require('../data/managers/volume-mapping-manager') +const ConfigMapManager = require('../data/managers/config-map-manager') +const SecretManager = require('../data/managers/secret-manager') +const MicroservicesService = require('./microservices-service') +const MicroservicePortService = require('./microservice-ports/microservice-port') +const VolumeMountService = require('./volume-mount-service') +const constants = require('../helpers/constants') +const isEqual = require('lodash/isEqual') +const Op = require('sequelize').Op +const { VOLUME_MAPPING_DEFAULT } = require('../helpers/constants') + +const CONTROLLER_MS_NAME = 'controller' +const SERVICE_ACCOUNT_VOLUME_TYPE = 'serviceAccount' + +function _parseFogAvailableRuntimes (fog) { + if (!fog || fog.availableRuntimes == null || fog.availableRuntimes === '') { + return [] + } + if (Array.isArray(fog.availableRuntimes)) { + return fog.availableRuntimes + } + try { + const parsed = JSON.parse(fog.availableRuntimes) + return Array.isArray(parsed) ? parsed : [] + } catch (error) { + return [] + } +} + +function _validateMicroserviceRuntime (runtime, fog) { + if (runtime == null || runtime === '') { + return + } + const availableRuntimes = _parseFogAvailableRuntimes(fog) + if (!availableRuntimes.includes(runtime)) { + const agentLabel = fog.name || fog.uuid || 'agent' + throw new Errors.ValidationError( + `Runtime '${runtime}' is not available on agent '${agentLabel}'` + ) + } +} + +function _validateImageFogType (name, fog, images) { + let found = false + for (const image of images) { + if (image.fogTypeId === fog.fogTypeId && image.containerImage) { + found = true + break + } + } + if (!found) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.MISSING_IMAGE, name)) + } +} + +function _validateMicroserviceConfig (config) { + if (config) { + return config.split('\\"').join('"').split('"').join('\"') // eslint-disable-line no-useless-escape + } + return '{}' +} + +function _rejectServiceAccountVolumeMappings (volumeMappings) { + if (!volumeMappings) { + return + } + for (const mapping of volumeMappings) { + const type = mapping.type || VOLUME_MAPPING_DEFAULT + if (type === SERVICE_ACCOUNT_VOLUME_TYPE) { + throw new Errors.ValidationError( + 'Volume mappings of type serviceAccount are system-managed and cannot be set by users' + ) + } + } +} + +function _validateVolumeMappingFields (volumeMappings) { + _rejectServiceAccountVolumeMappings(volumeMappings) + if (!volumeMappings) { + return + } + for (const mapping of volumeMappings) { + mapping.type = mapping.type || VOLUME_MAPPING_DEFAULT + if (mapping.type === 'volume' && (!/^[a-zA-Z0-9_.-]/.test(mapping.hostDestination))) { + throw new Errors.InvalidArgumentError('hostDestination includes invalid characters for a local volume name, only ' + + '"[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed. If you intended to pass a host directory, use type: bind') + } + if (mapping.type === 'volumeMount') { + if (!mapping.hostDestination || mapping.hostDestination === '') { + throw new Errors.ValidationError('hostDestination is required when type is volumeMount') + } + } + } +} + +function _validateKeyPath (data, keyPath, resourceName, resourceType, volumeMountName) { + if (!keyPath || keyPath === '') { + return true + } + if (data[keyPath] !== undefined && data[keyPath] !== null) { + return true + } + const keyPathWithSlash = keyPath.endsWith('/') ? keyPath : `${keyPath}/` + const hasMatchingKey = Object.keys(data).some((key) => key.startsWith(keyPathWithSlash)) + if (hasMatchingKey) { + return true + } + if (resourceType === 'Secret') { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.SECRET_KEY_NOT_FOUND_IN_VOLUME_MOUNT, keyPath, resourceName, volumeMountName)) + } + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.CONFIGMAP_KEY_NOT_FOUND_IN_VOLUME_MOUNT, keyPath, resourceName, volumeMountName)) +} + +async function _validateVolumeMountReference (hostDestination, type, fogUuid, transaction) { + if (!hostDestination || typeof hostDestination !== 'string' || type !== 'volumeMount') { + return + } + + const parts = hostDestination.split('/') + const volumeMountName = parts[0] + const keyPath = parts.length > 1 ? parts.slice(1).join('/') : null + + if (!volumeMountName) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_VOLUME_MOUNT_REFERENCE_FOR_VOLUME_MAPPING, 'Volume mount name cannot be empty')) + } + + let volumeMount + try { + volumeMount = await VolumeMountService.getVolumeMountEndpoint(volumeMountName, transaction) + } catch (error) { + if (error instanceof Errors.NotFoundError) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.VOLUME_MOUNT_NOT_FOUND, volumeMountName)) + } + throw error + } + + if (keyPath && keyPath !== '') { + if (volumeMount.secretName) { + const secret = await SecretManager.getSecret(volumeMount.secretName, transaction) + if (!secret) { + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.SECRET_NOT_FOUND, volumeMount.secretName)) + } + _validateKeyPath(secret.data, keyPath, volumeMount.secretName, 'Secret', volumeMountName) + } else if (volumeMount.configMapName) { + const configMap = await ConfigMapManager.getConfigMap(volumeMount.configMapName, transaction) + if (!configMap) { + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.CONFIGMAP_NOT_FOUND, volumeMount.configMapName)) + } + _validateKeyPath(configMap.data, keyPath, volumeMount.configMapName, 'ConfigMap', volumeMountName) + } else { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_VOLUME_MOUNT_REFERENCE_FOR_VOLUME_MAPPING, `Volume mount ${volumeMountName} does not have a secret or configmap associated`)) + } + } + + const linkedFogUuids = await VolumeMountService.findVolumeMountedFogNodes(volumeMountName, transaction) + if (!linkedFogUuids.includes(fogUuid)) { + await VolumeMountService.linkVolumeMountEndpoint(volumeMountName, [fogUuid], transaction) + } +} + +async function _validateVolumeMappingsForFog (volumeMappings, fogUuid, transaction) { + _validateVolumeMappingFields(volumeMappings) + if (!volumeMappings || !fogUuid) { + return + } + for (const volumeMapping of volumeMappings) { + if (volumeMapping.hostDestination) { + const type = volumeMapping.type || VOLUME_MAPPING_DEFAULT + await _validateVolumeMountReference(volumeMapping.hostDestination, type, fogUuid, transaction) + } + } +} + +async function _validateRegistry (registryId, transaction) { + const registry = await RegistryManager.findOne({ id: registryId }, transaction) + if (!registry) { + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_REGISTRY_ID, registryId)) + } +} + +async function _checkForDuplicateName (name, excludeUuid, applicationId, transaction) { + const where = { + name, + applicationId, + delete: false, + uuid: { [Op.ne]: excludeUuid } + } + const result = await MicroserviceManager.findOne(where, transaction) + if (result) { + throw new Errors.DuplicatePropertyError(AppHelper.formatMessage(ErrorMessages.DUPLICATE_NAME, name)) + } +} + +function _imagesChanged (newImages, existingImages) { + const oldContainerImages = existingImages.map((img) => img.containerImage) + const newContainerImages = newImages.map((img) => img.containerImage) + return !isEqual(newContainerImages, oldContainerImages) +} + +async function _createMicroserviceImages (microserviceUuid, images, transaction) { + const newImages = images.map((img) => ({ + ...img, + microserviceUuid + })) + return CatalogItemImageManager.bulkCreate(newImages, transaction) +} + +async function _createVolumeMappings (microserviceUuid, volumeMappings, transaction) { + if (!volumeMappings || !volumeMappings.length) { + return + } + const mappings = volumeMappings.map((volumeMapping) => ({ + microserviceUuid, + hostDestination: volumeMapping.hostDestination, + containerDestination: volumeMapping.containerDestination, + accessMode: volumeMapping.accessMode, + type: volumeMapping.type || VOLUME_MAPPING_DEFAULT + })) + await VolumeMappingManager.bulkCreate(mappings, transaction) +} + +async function _updateVolumeMappings (microserviceUuid, volumeMappings, fogUuid, transaction) { + await _validateVolumeMappingsForFog(volumeMappings, fogUuid, transaction) + await VolumeMappingManager.delete({ microserviceUuid }, transaction) + await _createVolumeMappings(microserviceUuid, volumeMappings, transaction) +} + +async function _updateEnv (env, microserviceUuid, transaction) { + await MicroserviceEnvManager.delete({ microserviceUuid }, transaction) + for (const envData of env) { + await MicroserviceEnvManager.create({ + microserviceUuid, + key: envData.key, + value: envData.value + }, transaction) + } +} + +async function _updateImages (images, microserviceUuid, transaction) { + await CatalogItemImageManager.delete({ microserviceUuid }, transaction) + await _createMicroserviceImages(microserviceUuid, images, transaction) +} + +async function _updatePorts (ports, microservice, transaction) { + await MicroservicePortService.deletePortMappings(microservice, transaction) + for (const mapping of ports) { + await MicroservicePortService.createPortMapping(microservice, mapping, transaction) + } +} + +async function _createControllerMicroservice (registerData, fog, application, transaction) { + await _checkForDuplicateName(CONTROLLER_MS_NAME, registerData.uuid, application.id, transaction) + + const microserviceData = { + uuid: registerData.uuid, + name: CONTROLLER_MS_NAME, + config: _validateMicroserviceConfig(registerData.config), + iofogUuid: fog.uuid, + hostNetworkMode: registerData.hostNetworkMode, + runtime: registerData.runtime, + registryId: registerData.registryId, + schedule: 0, + logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE * 1, + applicationId: application.id, + isController: true + } + + const microservice = await MicroserviceManager.create( + AppHelper.deleteUndefinedFields(microserviceData), + transaction + ) + + await _createMicroserviceImages(microservice.uuid, registerData.images, transaction) + + if (registerData.ports) { + await MicroservicePortService.validatePortMappings({ ports: registerData.ports, iofogUuid: fog.uuid }, transaction) + for (const mapping of registerData.ports) { + await MicroservicePortService.createPortMapping(microservice, mapping, transaction) + } + } + + if (registerData.env) { + for (const env of registerData.env) { + await MicroserviceEnvManager.create({ + microserviceUuid: microservice.uuid, + key: env.key, + value: env.value + }, transaction) + } + } + + await _createVolumeMappings(microservice.uuid, registerData.volumeMappings, transaction) + + await MicroserviceStatusManager.create({ microserviceUuid: microservice.uuid }, transaction) + await MicroserviceExecStatusManager.create({ microserviceUuid: microservice.uuid }, transaction) + + await MicroservicesService.updateChangeTracking(false, fog.uuid, transaction) + + return microservice +} + +async function _updateControllerMicroservice (existing, registerData, fog, transaction) { + const existingImages = await CatalogItemImageManager.findAll({ + microserviceUuid: existing.uuid + }, transaction) + + const config = registerData.config !== undefined + ? _validateMicroserviceConfig(registerData.config) + : undefined + + const microserviceUpdate = AppHelper.deleteUndefinedFields({ + isController: true, + registryId: registerData.registryId, + hostNetworkMode: registerData.hostNetworkMode, + runtime: registerData.runtime, + config, + rebuild: false + }) + + if (registerData.images && registerData.images.length > 0 && _imagesChanged(registerData.images, existingImages)) { + await _updateImages(registerData.images, existing.uuid, transaction) + microserviceUpdate.rebuild = true + } + + microserviceUpdate.rebuild = microserviceUpdate.rebuild || !!( + (registerData.hostNetworkMode !== undefined && existing.hostNetworkMode !== registerData.hostNetworkMode) || + (registerData.runtime !== undefined && existing.runtime !== registerData.runtime) || + (config !== undefined && existing.config !== config) || + registerData.env || + registerData.volumeMappings || + registerData.ports + ) + + const updatedMicroservice = await MicroserviceManager.updateAndFind( + { uuid: existing.uuid }, + microserviceUpdate, + transaction + ) + + if (registerData.ports) { + await MicroservicePortService.validatePortMappings({ ports: registerData.ports, iofogUuid: fog.uuid }, transaction) + await _updatePorts(registerData.ports, updatedMicroservice, transaction) + } + + if (registerData.volumeMappings) { + await _updateVolumeMappings(existing.uuid, registerData.volumeMappings, fog.uuid, transaction) + } + + if (registerData.env) { + await _updateEnv(registerData.env, existing.uuid, transaction) + } + + await MicroservicesService.updateChangeTracking(true, fog.uuid, transaction) + + return updatedMicroservice +} + +async function registerControllerMicroservice (registerData, fog, transaction) { + await Validator.validate(registerData, Validator.schemas.controllerRegister) + + if (!fog || !fog.isSystem) { + throw new Errors.ForbiddenError('Controller register is only available on system fogs') + } + + registerData.name = registerData.name || CONTROLLER_MS_NAME + + if (!registerData.images || !registerData.images.length) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.MICROSERVICE_DOES_NOT_HAVE_IMAGES, registerData.name)) + } + + await _validateRegistry(registerData.registryId, transaction) + _validateMicroserviceRuntime(registerData.runtime, fog) + _validateImageFogType(registerData.name, fog, registerData.images) + _validateVolumeMappingFields(registerData.volumeMappings) + await _validateVolumeMappingsForFog(registerData.volumeMappings, fog.uuid, transaction) + + if (registerData.ports) { + await MicroservicePortService.validatePortMappings({ ports: registerData.ports, iofogUuid: fog.uuid }, transaction) + } + + const existing = await MicroserviceManager.findOne({ uuid: registerData.uuid }, transaction) + if (existing && existing.iofogUuid !== fog.uuid) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, registerData.uuid)) + } + + const application = await ensureSystemApplication(fog, transaction) + + if (existing) { + await _updateControllerMicroservice(existing, registerData, fog, transaction) + } else { + await _createControllerMicroservice(registerData, fog, application, transaction) + } + + return { uuid: registerData.uuid } +} + +const bypassOptions = { bypassQueue: true } + +module.exports = { + registerControllerMicroservice: TransactionDecorator.generateTransaction(registerControllerMicroservice, bypassOptions) +} diff --git a/test/src/services/controller-ms-service.test.js b/test/src/services/controller-ms-service.test.js new file mode 100644 index 00000000..a3659b71 --- /dev/null +++ b/test/src/services/controller-ms-service.test.js @@ -0,0 +1,234 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const ControllerMsService = require('../../../src/services/controller-ms-service') +const Validator = require('../../../src/schemas') +const MicroserviceManager = require('../../../src/data/managers/microservice-manager') +const MicroserviceStatusManager = require('../../../src/data/managers/microservice-status-manager') +const MicroserviceExecStatusManager = require('../../../src/data/managers/microservice-exec-status-manager') +const CatalogItemImageManager = require('../../../src/data/managers/catalog-item-image-manager') +const MicroserviceEnvManager = require('../../../src/data/managers/microservice-env-manager') +const RegistryManager = require('../../../src/data/managers/registry-manager') +const VolumeMappingManager = require('../../../src/data/managers/volume-mapping-manager') +const MicroservicesService = require('../../../src/services/microservices-service') +const MicroservicePortService = require('../../../src/services/microservice-ports/microservice-port') +const ApplicationManager = require('../../../src/data/managers/application-manager') +const Errors = require('../../../src/helpers/errors') + +describe('Controller MS Service', () => { + def('subject', () => ControllerMsService) + def('sandbox', () => sinon.createSandbox()) + + const transaction = {} + + const fogUuid = 'system-fog-uuid' + const msUuid = 'controller-ms-uuid' + + const systemFog = { + uuid: fogUuid, + name: 'system-fog', + isSystem: true, + fogTypeId: 1, + availableRuntimes: JSON.stringify(['io.containerd.runc.v2']) + } + + const nonSystemFog = { + uuid: 'regular-fog-uuid', + name: 'regular-fog', + isSystem: false, + fogTypeId: 1 + } + + const application = { + id: 99, + name: 'system-system-fog', + isSystem: true + } + + const registerData = { + uuid: msUuid, + images: [{ containerImage: 'controller:latest', fogTypeId: 1 }], + registryId: 1, + ports: [{ internal: 8080, external: 8080, protocol: 'tcp' }], + volumeMappings: [{ + hostDestination: '/data', + containerDestination: '/data', + accessMode: 'rw', + type: 'bind' + }], + env: [{ key: 'CONTROL_PLANE', value: 'Remote' }], + config: '{}', + hostNetworkMode: false, + runtime: 'io.containerd.runc.v2' + } + + afterEach(() => $sandbox.restore()) + + describe('.registerControllerMicroservice()', () => { + def('fog', () => systemFog) + def('body', () => ({ ...registerData })) + def('subject', () => $subject.registerControllerMicroservice($body, $fog, transaction)) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(RegistryManager, 'findOne').resolves({ id: 1 }) + $sandbox.stub(ApplicationManager, 'findOne').resolves(application) + $sandbox.stub(MicroserviceManager, 'findOne').callsFake((where) => { + if (where.uuid === msUuid) { + return Promise.resolve(null) + } + return Promise.resolve(null) + }) + $sandbox.stub(MicroserviceManager, 'delete').resolves() + $sandbox.stub(MicroserviceManager, 'create').resolves({ uuid: msUuid, name: 'controller' }) + $sandbox.stub(MicroserviceManager, 'updateAndFind').resolves({ uuid: msUuid, name: 'controller' }) + $sandbox.stub(CatalogItemImageManager, 'bulkCreate').resolves() + $sandbox.stub(CatalogItemImageManager, 'findAll').resolves([]) + $sandbox.stub(CatalogItemImageManager, 'delete').resolves() + $sandbox.stub(MicroservicePortService, 'validatePortMappings').resolves() + $sandbox.stub(MicroservicePortService, 'createPortMapping').resolves() + $sandbox.stub(MicroservicePortService, 'deletePortMappings').resolves() + $sandbox.stub(MicroserviceEnvManager, 'create').resolves() + $sandbox.stub(MicroserviceEnvManager, 'delete').resolves() + $sandbox.stub(VolumeMappingManager, 'bulkCreate').resolves() + $sandbox.stub(VolumeMappingManager, 'delete').resolves() + $sandbox.stub(MicroserviceStatusManager, 'create').resolves() + $sandbox.stub(MicroserviceExecStatusManager, 'create').resolves() + $sandbox.stub(MicroservicesService, 'updateChangeTracking').resolves() + $sandbox.stub(MicroservicesService, 'injectServiceAccountVolume').resolves() + $sandbox.stub(MicroservicesService, 'createOrUpdateServiceAccountForMicroservice').resolves() + }) + + context('on non-system fog', () => { + def('fog', () => nonSystemFog) + + it('rejects with ForbiddenError', async () => { + await expect($subject).to.be.rejectedWith(Errors.ForbiddenError) + }) + }) + + it('returns uuid only on create', async () => { + const result = await $subject + expect(result).to.deep.equal({ uuid: msUuid }) + }) + + it('creates microservice with client uuid and isController true', async () => { + await $subject + expect(MicroserviceManager.create).to.have.been.calledWith( + sinon.match({ + uuid: msUuid, + name: 'controller', + iofogUuid: fogUuid, + applicationId: application.id, + isController: true, + registryId: 1 + }), + transaction + ) + }) + + it('defaults name to controller when omitted', async () => { + def('body', () => { + const { name, ...rest } = registerData + return rest + }) + await $subject + expect(MicroserviceManager.create).to.have.been.calledWith( + sinon.match({ name: 'controller' }), + transaction + ) + }) + + it('uses microserviceList change tracking on create', async () => { + await $subject + expect(MicroservicesService.updateChangeTracking).to.have.been.calledWith(false, fogUuid, transaction) + }) + + it('does not inject service account volume on create', async () => { + await $subject + expect(MicroservicesService.injectServiceAccountVolume).to.not.have.been.called + expect(MicroservicesService.createOrUpdateServiceAccountForMicroservice).to.not.have.been.called + }) + + context('when microservice already exists', () => { + const existing = { + uuid: msUuid, + name: 'controller', + iofogUuid: fogUuid, + applicationId: application.id, + hostNetworkMode: false, + runtime: 'io.containerd.runc.v2', + config: '{}', + registryId: 1 + } + + beforeEach(() => { + MicroserviceManager.findOne.callsFake((where) => { + if (where.uuid === msUuid) { + return Promise.resolve(existing) + } + return Promise.resolve(null) + }) + CatalogItemImageManager.findAll.resolves([{ + containerImage: 'controller:old', + fogTypeId: 1 + }]) + }) + + it('upserts existing microservice and returns uuid', async () => { + const result = await $subject + expect(result).to.deep.equal({ uuid: msUuid }) + expect(MicroserviceManager.updateAndFind).to.have.been.called + }) + + it('preserves isController on update', async () => { + await $subject + expect(MicroserviceManager.updateAndFind).to.have.been.calledWith( + { uuid: msUuid }, + sinon.match({ isController: true }), + transaction + ) + }) + + it('uses microserviceCommon change tracking on update', async () => { + await $subject + expect(MicroservicesService.updateChangeTracking).to.have.been.calledWith(true, fogUuid, transaction) + }) + + it('rejects uuid registered on a different fog', async () => { + MicroserviceManager.findOne.callsFake((where) => { + if (where.uuid === msUuid) { + return Promise.resolve({ + ...existing, + iofogUuid: 'other-fog-uuid' + }) + } + return Promise.resolve(null) + }) + await expect($subject).to.be.rejectedWith(Errors.ValidationError) + }) + }) + + context('when runtime is not available on fog', () => { + def('body', () => ({ + ...registerData, + runtime: 'missing-runtime' + })) + + it('rejects with ValidationError', async () => { + await expect($subject).to.be.rejectedWith(Errors.ValidationError) + }) + }) + + context('when name is invalid', () => { + beforeEach(() => { + Validator.validate.restore() + $sandbox.stub(Validator, 'validate').rejects(new Errors.ValidationError('Invalid name')) + }) + + it('rejects with ValidationError', async () => { + await expect($subject).to.be.rejectedWith(Errors.ValidationError) + }) + }) + }) +}) From dd7071c0fb14429139c2ff26d7b991bd2cdf4bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 14:43:04 +0300 Subject: [PATCH 30/75] Expose isController in agent microservice list and block user mutations. User DELETE returns 403 ForbiddenError for controller microservices. User PATCH returns 400 ValidationError, matching system catalog behavior. --- src/helpers/error-messages.js | 1 + src/services/agent-service.js | 1 + src/services/microservices-service.js | 6 ++ test/src/services/agent-service.test.js | 4 + .../services/microservices-service.test.js | 79 ++++++++++++++++++- 5 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/helpers/error-messages.js b/src/helpers/error-messages.js index 47bdf3b8..e848a29a 100644 --- a/src/helpers/error-messages.js +++ b/src/helpers/error-messages.js @@ -89,6 +89,7 @@ module.exports = { SYSTEM_CATALOG_ITEM_DELETE: 'Catalog item id {} is system and can\'t be deleted', SYSTEM_MICROSERVICE_UPDATE: 'Microservice uuid {} is system and can\'t be updated', SYSTEM_MICROSERVICE_DELETE: 'Microservice uuid {} is system and can\'t be deleted', + CONTROLLER_MICROSERVICE_DELETE: 'Microservice uuid {} is the controller microservice and can\'t be deleted', INVALID_CONFIG_KEY: 'Unkown config key \'{}\'', INVALID_ROUTER: 'Invalid router \'{}\'', INVALID_NATS: 'Invalid router \'{}\'', diff --git a/src/services/agent-service.js b/src/services/agent-service.js index 6125c525..03d426a6 100644 --- a/src/services/agent-service.js +++ b/src/services/agent-service.js @@ -472,6 +472,7 @@ const getAgentMicroservices = async function (fog, transaction) { capDrop, isRouter, isNats, + isController: microservice.isController, execEnabled: microservice.execEnabled, schedule: microservice.schedule } diff --git a/src/services/microservices-service.js b/src/services/microservices-service.js index 5c19148f..0e8b3a2a 100644 --- a/src/services/microservices-service.js +++ b/src/services/microservices-service.js @@ -1264,6 +1264,9 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i if (microservice.catalogItem && microservice.catalogItem.category === 'SYSTEM') { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.SYSTEM_MICROSERVICE_UPDATE, microserviceUuid)) } + if (microservice.isController) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.SYSTEM_MICROSERVICE_UPDATE, microserviceUuid)) + } _validateMicroserviceSchedule(microserviceDataUpdate.schedule, false) @@ -1697,6 +1700,9 @@ async function deleteMicroserviceEndPoint (microserviceUuid, microserviceData, i if (!isCLI && microservice.catalogItem && microservice.catalogItem.category === 'SYSTEM') { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.SYSTEM_MICROSERVICE_DELETE, microserviceUuid)) } + if (!isCLI && microservice.isController) { + throw new Errors.ForbiddenError(AppHelper.formatMessage(ErrorMessages.CONTROLLER_MICROSERVICE_DELETE, microserviceUuid)) + } const existingService = await ServiceManager.findOne({ type: `microservice`, resource: microservice.uuid }, transaction) if (existingService) { diff --git a/test/src/services/agent-service.test.js b/test/src/services/agent-service.test.js index 5532cb23..042ff7d2 100644 --- a/test/src/services/agent-service.test.js +++ b/test/src/services/agent-service.test.js @@ -1098,6 +1098,7 @@ describe('Agent Service', () => { volumeMappings: 'testVolumeMappings', delete: false, deleteWithCleanup: false, + isController: true, catalogItem: { images: [{ archId: 1, @@ -1247,6 +1248,9 @@ describe('Agent Service', () => { expect(msvc.registryId).to.equal(microserviceResponse.microservices[0].registryId) expect(msvc.cmd).to.deep.equal(microserviceResponse.microservices[0].cmd) expect(msvc.extraHosts).to.deep.equal(microserviceResponse.microservices[0].extraHosts) + expect(msvc.isController).to.equal(true) + expect(msvc.isRouter).to.equal(false) + expect(msvc.isNats).to.equal(false) }) }) }) diff --git a/test/src/services/microservices-service.test.js b/test/src/services/microservices-service.test.js index 81add8b1..76fbc61f 100644 --- a/test/src/services/microservices-service.test.js +++ b/test/src/services/microservices-service.test.js @@ -1585,16 +1585,18 @@ describe('Microservices Service', () => { const microserviceUuid = 'msvcToDeleteUUID' const isCLI = false - const user = { - id: 15 - } const microserviceData = { uuid: microserviceUuid, iofogUuid: 'msvciofoguuid' } - def('subject', () => $subject.deleteMicroserviceEndPoint(microserviceUuid, microserviceData, user, isCLI, transaction)) + const ServiceManager = require('../../../src/data/managers/service-manager') + const NatsAuthService = require('../../../src/services/nats-auth-service') + const MicroservicePortService = require('../../../src/services/microservice-ports/microservice-port') + const RbacServiceAccountManager = require('../../../src/data/managers/rbac-service-account-manager') + + def('subject', () => $subject.deleteMicroserviceEndPoint(microserviceUuid, microserviceData, isCLI, transaction)) def('findMicroserviceResponse', () => Promise.resolve(microserviceData)) def('findPortMappings', () => Promise.resolve([])) @@ -1605,7 +1607,20 @@ describe('Microservices Service', () => { $sandbox.stub(ChangeTrackingService, 'update') }) + const stubDeleteDependencies = ({ keepPortDeletion = false } = {}) => { + $sandbox.stub(ServiceManager, 'findOne').resolves(null) + $sandbox.stub(NatsAuthService, 'revokeMicroserviceUser').resolves() + if (!keepPortDeletion) { + $sandbox.stub(MicroservicePortService, 'deletePortMappings').resolves() + } + $sandbox.stub(RbacServiceAccountManager, 'deleteByMicroserviceUuid').resolves() + if (!keepPortDeletion) { + $sandbox.stub(MicroserviceManager, 'update').resolves() + } + } + it('should delete the microservice', async () => { + stubDeleteDependencies() await $subject expect(MicroserviceManager.delete).to.have.been.calledWith({uuid: microserviceUuid}, transaction) return expect(ChangeTrackingService.update).to.have.been.calledWith(microserviceData.iofogUuid, ChangeTrackingService.events.microserviceList, transaction) @@ -1639,6 +1654,22 @@ describe('Microservices Service', () => { }) }) + context('when microservice is controller', () => { + def('findMicroserviceResponse', () => Promise.resolve({ + uuid: microserviceUuid, + iofogUuid: microserviceData.iofogUuid, + isController: true + })) + it('should fail with ForbiddenError', async () => { + try { + await $subject + } catch (e) { + return expect(e).to.be.instanceOf(Errors.ForbiddenError) + } + return expect(true).to.eql(false) + }) + }) + context('when there are ports', () => { const publicPort = { id: 1, @@ -1666,6 +1697,7 @@ describe('Microservices Service', () => { def('findPortMappings', () => Promise.resolve(portMappings)) beforeEach(() => { + stubDeleteDependencies({ keepPortDeletion: true }) $sandbox.stub(MicroservicePortManager, 'delete') $sandbox.stub(MicroserviceManager, 'update') $sandbox.stub(MicroserviceManager, 'findOne') @@ -2173,4 +2205,43 @@ describe('Microservices Service', () => { }) }) }) + + describe('controller microservice user guards', () => { + const transaction = {} + const microserviceUuid = 'controller-ms-uuid' + const images = [{ fogTypeId: 1, containerImage: 'controller:latest' }] + const microservice = { + uuid: microserviceUuid, + name: 'controller', + applicationId: 16, + iofogUuid: 'system-fog-uuid', + registryId: 1, + isController: true, + catalogItem: { images } + } + + describe('.updateMicroserviceEndPoint()', () => { + def('subject', () => MicroservicesService.updateMicroserviceEndPoint( + microserviceUuid, + { config: '{}' }, + false, + transaction + )) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(MicroserviceManager, 'findOne').resolves(microservice) + $sandbox.stub(MicroserviceManager, 'findOneWithCategory').resolves(microservice) + $sandbox.stub(CatalogItemImageManager, 'findAll').resolves(images) + $sandbox.stub(ApplicationManager, 'findOne').resolves({ id: microservice.applicationId, natsAccess: false }) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((obj) => obj) + $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid: microservice.iofogUuid, fogTypeId: 1 }) + $sandbox.stub(ioFogService, 'getFog').resolves({ uuid: microservice.iofogUuid, fogTypeId: 1 }) + }) + + it('blocks user update when isController', async () => { + await expect($subject).to.be.rejectedWith(Errors.ValidationError) + }) + }) + }) }) From 685f4cec8ef1e14ecf85cc92cab1fdb74d061c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 14:43:09 +0300 Subject: [PATCH 31/75] Document controller register endpoint and isController in OpenAPI. Adds ControllerRegisterRequest/Response schemas and isController on the agent microservice list model. --- docs/swagger.yaml | 94 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index fa6e72fd..2fa45295 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1228,6 +1228,43 @@ paths: description: Not Authorized "500": description: Internal Server Error + /agent/controller/register: + post: + tags: + - Agent + summary: Register ControlPlane controller microservice + description: >- + Agent-only endpoint (fog token). Edgelet registers the controller workload on system fogs. + Not available via user RBAC — do not add to rbac-resources.yaml user resources. + operationId: registerControllerMicroservice + security: + - authToken: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ControllerRegisterRequest" + responses: + "200": + description: Success + headers: + X-Timestamp: + description: FogController server timestamp + schema: + type: number + content: + application/json: + schema: + $ref: "#/components/schemas/ControllerRegisterResponse" + "400": + description: Bad Request — invalid body or name other than controller + "401": + description: Not Authorized + "403": + description: Forbidden — authenticated fog is not a system fog + "500": + description: Internal Server Error /agent/microservices: get: tags: @@ -6991,6 +7028,63 @@ components: type: string idConsumer: type: boolean + isRouter: + type: boolean + description: True when microservice is the system router + isNats: + type: boolean + description: True when microservice is the system NATS broker + isController: + type: boolean + description: True when microservice is the ControlPlane controller workload (DB column) + ControllerRegisterRequest: + type: object + description: Slim register body for Edgelet ControlPlane controller workload + required: + - uuid + - images + - registryId + properties: + uuid: + type: string + description: Edgelet-generated stable uuid for the controller microservice + name: + type: string + enum: + - controller + description: Must be controller (default controller if omitted) + images: + type: array + minItems: 1 + maxItems: 2 + items: + $ref: "#/components/schemas/MicroserviceContainerImage" + registryId: + type: integer + ports: + type: array + items: + $ref: "#/components/schemas/PortMappingsResponse" + volumeMappings: + type: array + items: + $ref: "#/components/schemas/VolumeMappingRequest" + env: + type: array + items: + $ref: "#/components/schemas/AgentEnvRequest" + config: + type: string + hostNetworkMode: + type: boolean + runtime: + type: string + ControllerRegisterResponse: + type: object + properties: + uuid: + type: string + description: Same uuid as the register request AgentEnvRequest: type: object properties: From fff4ba3de41f0fd5c02c1de12a2e1d1e50c9f061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 18:10:52 +0300 Subject: [PATCH 32/75] Support up to four per-architecture images with shared validation. Centralize archId checks in arch-images helper and apply them to catalog, microservices, YAML import, and OpenAPI schemas. --- docs/swagger.yaml | 4 +- src/helpers/arch-images.js | 113 ++++++++++++++++++ src/schemas/catalog.js | 4 +- src/schemas/controller-register.js | 2 +- src/schemas/microservice.js | 4 +- src/services/catalog-service.js | 36 ++---- src/services/controller-ms-service.js | 17 +-- src/services/microservices-service.js | 68 +++-------- src/services/yaml-parser-service.js | 19 +-- .../controllers/catalog-controller.test.js | 8 +- test/src/helpers/arch-images.test.js | 59 +++++++++ test/src/services/catalog-service.test.js | 74 +++--------- .../services/controller-ms-service.test.js | 8 +- .../services/microservices-service.test.js | 42 +++---- test/src/services/yaml-parser-service.test.js | 23 ++++ 15 files changed, 288 insertions(+), 193 deletions(-) create mode 100644 src/helpers/arch-images.js create mode 100644 test/src/helpers/arch-images.test.js diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 2fa45295..79dbeaef 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -7056,7 +7056,7 @@ components: images: type: array minItems: 1 - maxItems: 2 + maxItems: 4 items: $ref: "#/components/schemas/MicroserviceContainerImage" registryId: @@ -7315,6 +7315,8 @@ components: enum: - 1 - 2 + - 3 + - 4 CreateUpdateCatalogItemRequestBody: type: object properties: diff --git a/src/helpers/arch-images.js b/src/helpers/arch-images.js new file mode 100644 index 00000000..776455f3 --- /dev/null +++ b/src/helpers/arch-images.js @@ -0,0 +1,113 @@ +const Errors = require('./errors') +const AppHelper = require('./app-helper') +const ErrorMessages = require('./error-messages') + +const DEPLOY_ARCH_IDS = [1, 2, 3, 4] + +function validateUniqueArchIds (images) { + if (!images || !images.length) { + return + } + const seen = new Set() + for (const image of images) { + if (image.archId == null) { + continue + } + if (seen.has(image.archId)) { + throw new Errors.ValidationError(`Duplicate archId '${image.archId}' in images`) + } + seen.add(image.archId) + } +} + +function validateImageMatchesFogArch (microserviceName, fog, images) { + if (!images || !images.length) { + return + } + let found = false + for (const image of images) { + if (image.archId === fog.archId && image.containerImage) { + found = true + break + } + } + if (!found) { + throw new Errors.ValidationError( + AppHelper.formatMessage(ErrorMessages.MISSING_IMAGE, microserviceName) + ) + } +} + +function validateImagesAgainstCatalog (catalogItem, images) { + const allImagesEmpty = images.reduce((result, b) => result && b.containerImage === '', true) + if (allImagesEmpty) { + return + } + for (const img of images) { + let found = false + for (const catalogImg of catalogItem.images) { + if (catalogImg.archId === img.archId) { + found = true + } + if (found === true && img.containerImage !== '' && catalogImg.containerImage !== img.containerImage) { + throw new Errors.ValidationError( + AppHelper.formatMessage(ErrorMessages.CATALOG_NOT_MATCH_IMAGES, `${catalogItem.id}`) + ) + } + } + if (!found) { + throw new Errors.ValidationError( + AppHelper.formatMessage(ErrorMessages.CATALOG_NOT_MATCH_IMAGES, `${catalogItem.id}`) + ) + } + } +} + +function imagesAreEqual (leftImages, rightImages) { + const normalize = (images) => images + .map((image) => ({ archId: image.archId, containerImage: image.containerImage })) + .sort((a, b) => a.archId - b.archId) + + const left = normalize(leftImages) + const right = normalize(rightImages) + if (left.length !== right.length) { + return false + } + return left.every((image, index) => { + const other = right[index] + return image.archId === other.archId && image.containerImage === other.containerImage + }) +} + +function mapYamlImagesToArchList (images) { + const imgs = [] + if (!images || typeof images !== 'object') { + return imgs + } + const yamlKeyToArchId = [ + ['amd64', 1], + ['x86', 1], + ['arm64', 2], + ['riscv64', 3], + ['riscv', 3], + ['arm', 4] + ] + for (const [key, archId] of yamlKeyToArchId) { + if (images[key] != null) { + imgs.push({ + archId, + containerImage: images[key] + }) + } + } + return imgs +} + +module.exports = { + DEPLOY_ARCH_IDS, + validateUniqueArchIds, + validateImageMatchesFogArch, + validateImagesAgainstCatalog, + imagesAreEqual, + mapYamlImagesToArchList +} diff --git a/src/schemas/catalog.js b/src/schemas/catalog.js index 58fb3a1e..244d8bd5 100644 --- a/src/schemas/catalog.js +++ b/src/schemas/catalog.js @@ -28,7 +28,7 @@ const catalogItemCreate = { 'images': { 'type': 'array', 'minItems': 1, - 'maxItems': 2, + 'maxItems': 4, 'items': { '$ref': '/image' } }, 'inputType': { '$ref': '/type' }, @@ -54,7 +54,7 @@ const catalogItemUpdate = { 'configExample': { 'type': 'string' }, 'images': { 'type': 'array', - 'maxItems': 2, + 'maxItems': 4, 'items': { '$ref': '/image' } }, 'inputType': { '$ref': '/type' }, diff --git a/src/schemas/controller-register.js b/src/schemas/controller-register.js index 000a4776..ef84046e 100644 --- a/src/schemas/controller-register.js +++ b/src/schemas/controller-register.js @@ -20,7 +20,7 @@ const controllerRegister = { 'images': { 'type': 'array', 'minItems': 1, - 'maxItems': 2, + 'maxItems': 4, 'items': { '$ref': '/image' } }, 'registryId': { 'type': 'integer' }, diff --git a/src/schemas/microservice.js b/src/schemas/microservice.js index 5a29d437..0ad29c5e 100644 --- a/src/schemas/microservice.js +++ b/src/schemas/microservice.js @@ -16,7 +16,7 @@ const microserviceCreate = { }, 'images': { 'type': 'array', - 'maxItems': 2, + 'maxItems': 4, 'items': { '$ref': '/image' } }, 'registryId': { @@ -114,7 +114,7 @@ const microserviceUpdate = { }, 'images': { 'type': 'array', - 'maxItems': 2, + 'maxItems': 4, 'minItems': 1, 'items': { '$ref': '/image' } }, diff --git a/src/services/catalog-service.js b/src/services/catalog-service.js index a3de0cc3..5e20592c 100644 --- a/src/services/catalog-service.js +++ b/src/services/catalog-service.js @@ -13,6 +13,7 @@ const TransactionDecorator = require('../decorators/transaction-decorator') const AppHelper = require('../helpers/app-helper') +const { validateUniqueArchIds } = require('../helpers/arch-images') const Errors = require('../helpers/errors') const ErrorMessages = require('../helpers/error-messages') const CatalogItemManager = require('../data/managers/catalog-item-manager') @@ -241,29 +242,15 @@ const _createCatalogItem = async function (data, transaction) { } const _createCatalogImages = async function (data, catalogItem, transaction) { - const catalogItemImages = [ - { - fogTypeId: 1, - catalogItemId: catalogItem.id - }, - { - fogTypeId: 2, - catalogItemId: catalogItem.id - } - ] - if (data.images) { - for (const image of data.images) { - switch (image.fogTypeId) { - case 1: - catalogItemImages[0].containerImage = image.containerImage - break - case 2: - catalogItemImages[1].containerImage = image.containerImage - break - } - } + if (!data.images || !data.images.length) { + return [] } - + validateUniqueArchIds(data.images) + const catalogItemImages = data.images.map((image) => ({ + catalogItemId: catalogItem.id, + archId: image.archId, + containerImage: image.containerImage + })) return CatalogItemImageManager.bulkCreate(catalogItemImages, transaction) } @@ -348,13 +335,14 @@ const _updateCatalogItemImages = async function (data, transaction) { // } // } + validateUniqueArchIds(data.images) for (const image of data.images) { await CatalogItemImageManager.updateOrCreate({ catalogItemId: data.id, - fogTypeId: image.fogTypeId + archId: image.archId }, { catalogItemId: data.id, - fogTypeId: image.fogTypeId, + archId: image.archId, containerImage: image.containerImage }, transaction) } diff --git a/src/services/controller-ms-service.js b/src/services/controller-ms-service.js index 46a7f588..d77d21a6 100644 --- a/src/services/controller-ms-service.js +++ b/src/services/controller-ms-service.js @@ -65,17 +65,10 @@ function _validateMicroserviceRuntime (runtime, fog) { } } -function _validateImageFogType (name, fog, images) { - let found = false - for (const image of images) { - if (image.fogTypeId === fog.fogTypeId && image.containerImage) { - found = true - break - } - } - if (!found) { - throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.MISSING_IMAGE, name)) - } +const { validateImageMatchesFogArch } = require('../helpers/arch-images') + +function _validateImageArch (name, fog, images) { + validateImageMatchesFogArch(name, fog, images) } function _validateMicroserviceConfig (config) { @@ -395,7 +388,7 @@ async function registerControllerMicroservice (registerData, fog, transaction) { await _validateRegistry(registerData.registryId, transaction) _validateMicroserviceRuntime(registerData.runtime, fog) - _validateImageFogType(registerData.name, fog, registerData.images) + _validateImageArch(registerData.name, fog, registerData.images) _validateVolumeMappingFields(registerData.volumeMappings) await _validateVolumeMappingsForFog(registerData.volumeMappings, fog.uuid, transaction) diff --git a/src/services/microservices-service.js b/src/services/microservices-service.js index 0e8b3a2a..40f7bb0c 100644 --- a/src/services/microservices-service.js +++ b/src/services/microservices-service.js @@ -29,6 +29,12 @@ const MicroserviceStates = require('../enums/microservice-state') const VolumeMappingManager = require('../data/managers/volume-mapping-manager') const ChangeTrackingService = require('./change-tracking-service') const AppHelper = require('../helpers/app-helper') +const { + validateUniqueArchIds, + validateImageMatchesFogArch, + validateImagesAgainstCatalog, + imagesAreEqual +} = require('../helpers/arch-images') const Errors = require('../helpers/errors') const ErrorMessages = require('../helpers/error-messages') const { slugifyName } = require('../helpers/system-naming') @@ -51,7 +57,6 @@ const FogManager = require('../data/managers/iofog-manager') const MicroserviceExtraHostManager = require('../data/managers/microservice-extra-host-manager') const { VOLUME_MAPPING_DEFAULT } = require('../helpers/constants') const constants = require('../helpers/constants') -const isEqual = require('lodash/isEqual') const logger = require('../logger') const SERVICE_ACCOUNT_VOLUME_TYPE = 'serviceAccount' @@ -299,27 +304,6 @@ async function getSystemMicroserviceEndPoint (microserviceUuid, isCLI, transacti return _buildGetMicroserviceResponse(microservice.dataValues, transaction) } -function _validateImagesAgainstCatalog (catalogItem, images) { - const allImagesEmpty = images.reduce((result, b) => result && b.containerImage === '', true) - if (allImagesEmpty) { - return - } - for (const img of images) { - let found = false - for (const catalogImg of catalogItem.images) { - if (catalogImg.fogType === img.fogType) { - found = true - } - if (found === true && img.containerImage !== '' && catalogImg.containerImage !== img.containerImage) { - throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.CATALOG_NOT_MATCH_IMAGES, `${catalogItem.id}`)) - } - } - if (!found) { - throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.CATALOG_NOT_MATCH_IMAGES, `${catalogItem.id}`)) - } - } -} - async function _validateLocalAppHostTemplate (extraHost, templateArgs, msvc, fogUuid, transaction) { if (templateArgs.length !== 4) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_HOST_TEMPLATE, templateArgs.join('.'))) @@ -409,17 +393,8 @@ async function _validateExtraHosts (microserviceData, fogUuid, transaction) { return extraHosts } -function _validateImageFogType (microserviceData, fog, images) { - let found = false - for (const image of images) { - if (image.fogTypeId === fog.fogTypeId && image.containerImage) { - found = true - break - } - } - if (!found) { - throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.MISSING_IMAGE, microserviceData.name)) - } +function _validateImageArch (microserviceData, fog, images) { + validateImageMatchesFogArch(microserviceData.name, fog, images) } async function _findFog (microserviceData, isCLI, transaction) { @@ -563,15 +538,16 @@ async function createMicroserviceEndPoint (microserviceData, isCLI, transaction) if (microserviceData.catalogItemId) { // validate catalog item const catalogItem = await CatalogService.getCatalogItem(microserviceData.catalogItemId, isCLI, transaction) - _validateImagesAgainstCatalog(catalogItem, microserviceData.images || []) + validateImagesAgainstCatalog(catalogItem, microserviceData.images || []) microserviceData.images = catalogItem.images - _validateImageFogType(microserviceData, fog, catalogItem.images) + _validateImageArch(microserviceData, fog, catalogItem.images) // use catalog item's registryId if it is set if (catalogItem.registryId) { microserviceData.registryId = catalogItem.registryId } } else { - _validateImageFogType(microserviceData, fog, microserviceData.images) + validateUniqueArchIds(microserviceData.images) + _validateImageArch(microserviceData, fog, microserviceData.images) } if (!microserviceData.images || !microserviceData.images.length) { @@ -971,7 +947,7 @@ async function updateSystemMicroserviceEndPoint (microserviceUuid, microserviceD const iofogUuid = microserviceDataUpdate.iofogUuid || microservice.iofogUuid if (microserviceDataUpdate.catalogItemId) { const catalogItem = await CatalogService.getSystemCatalogItem(microserviceDataUpdate.catalogItemId, isCLI, transaction) - _validateImagesAgainstCatalog(catalogItem, microserviceDataUpdate.images || []) + validateImagesAgainstCatalog(catalogItem, microserviceDataUpdate.images || []) if (microserviceDataUpdate.catalogItemId !== undefined && microserviceDataUpdate.catalogItemId !== microservice.catalogItemId) { // Catalog item changed or removed, set rebuild flag microserviceDataUpdate.rebuild = true @@ -1020,7 +996,7 @@ async function updateSystemMicroserviceEndPoint (microserviceUuid, microserviceD } else { images = await microservice.getImages() } - _validateImageFogType(microserviceData, fog, images) + _validateImageArch(microserviceData, fog, images) const shouldValidateRuntime = microserviceDataUpdate.runtime !== undefined || (microserviceDataUpdate.iofogUuid && microserviceDataUpdate.iofogUuid !== microservice.iofogUuid) if (shouldValidateRuntime) { @@ -1275,7 +1251,7 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i const iofogUuid = microserviceDataUpdate.iofogUuid || microservice.iofogUuid if (microserviceDataUpdate.catalogItemId) { const catalogItem = await CatalogService.getCatalogItem(microserviceDataUpdate.catalogItemId, isCLI, transaction) - _validateImagesAgainstCatalog(catalogItem, microserviceDataUpdate.images || []) + validateImagesAgainstCatalog(catalogItem, microserviceDataUpdate.images || []) if (microserviceDataUpdate.catalogItemId !== undefined && microserviceDataUpdate.catalogItemId !== microservice.catalogItemId) { // Catalog item changed or removed, set rebuild flag microserviceDataUpdate.rebuild = true @@ -1324,7 +1300,7 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i } else { images = await microservice.getImages() } - _validateImageFogType(microserviceData, fog, images) + _validateImageArch(microserviceData, fog, images) const shouldValidateRuntime = microserviceDataUpdate.runtime !== undefined || (microserviceDataUpdate.iofogUuid && microserviceDataUpdate.iofogUuid !== microservice.iofogUuid) if (shouldValidateRuntime) { @@ -1673,15 +1649,7 @@ async function rebuildSystemMicroserviceEndPoint (microserviceUuid, isCLI, trans * @param {*} catalogImages */ const _checkIfMicroserviceImagesAreEqual = (microserviceDataUpdateImages, catalogImages) => { - const oldMicroservicesImages = [] - for (const images of catalogImages) { - oldMicroservicesImages.push(images.containerImage) - } - const newMicroserviceImages = [] - for (const images of microserviceDataUpdateImages) { - newMicroserviceImages.push(images.containerImage) - } - return isEqual(newMicroserviceImages, oldMicroservicesImages) + return imagesAreEqual(microserviceDataUpdateImages, catalogImages) } async function deleteMicroserviceEndPoint (microserviceUuid, microserviceData, isCLI, transaction) { @@ -2615,7 +2583,7 @@ async function _buildGetMicroserviceResponse (microservice, transaction) { res.capAdd = capAdds res.capDrop = capDrops res.extraHosts = extraHosts.map(eH => ({ name: eH.name, address: eH.template, value: eH.value })) - res.images = images.map(i => ({ containerImage: i.containerImage, fogTypeId: i.fogTypeId })) + res.images = images.map(i => ({ containerImage: i.containerImage, archId: i.archId })) if (status && status.length) { res.status = status[0] } diff --git a/src/services/yaml-parser-service.js b/src/services/yaml-parser-service.js index 7cfeeab5..5177c4d9 100644 --- a/src/services/yaml-parser-service.js +++ b/src/services/yaml-parser-service.js @@ -232,22 +232,9 @@ async function parseServiceFile (fileContent, options = {}) { } } -const mapImages = (images) => { - const imgs = [] - if (images.x86 != null) { - imgs.push({ - fogTypeId: 1, - containerImage: images.x86 - }) - } - if (images.arm != null) { - imgs.push({ - fogTypeId: 2, - containerImage: images.arm - }) - } - return imgs -} +const { mapYamlImagesToArchList } = require('../helpers/arch-images') + +const mapImages = (images) => mapYamlImagesToArchList(images) const parseMicroserviceImages = async (fileImages) => { // Could be undefined if patch call diff --git a/test/src/controllers/catalog-controller.test.js b/test/src/controllers/catalog-controller.test.js index de529857..c5aace5c 100644 --- a/test/src/controllers/catalog-controller.test.js +++ b/test/src/controllers/catalog-controller.test.js @@ -17,10 +17,10 @@ describe('Catalog Controller', () => { def('description', () => 'testDescription') def('category', () => 'testCategory') def('containerImage', () => 'testContainerImage') - def('fogTypeId', () => 'testFogTypeId') + def('archId', () => 'testFogTypeId') def('images', () => [{ containerImage: $containerImage, - fogTypeId: $fogTypeId, + archId: $archId, }]) def('publisher', () => 'testPublisher') def('diskRequired', () => 15) @@ -225,10 +225,10 @@ describe('Catalog Controller', () => { def('description', () => 'testDescription') def('category', () => 'testCategory') def('containerImage', () => 'testContainerImage') - def('fogTypeId', () => 'testFogTypeId') + def('archId', () => 'testFogTypeId') def('images', () => [{ containerImage: $containerImage, - fogTypeId: $fogTypeId, + archId: $archId, }]) def('publisher', () => 'testPublisher') def('diskRequired', () => 15) diff --git a/test/src/helpers/arch-images.test.js b/test/src/helpers/arch-images.test.js new file mode 100644 index 00000000..968f309d --- /dev/null +++ b/test/src/helpers/arch-images.test.js @@ -0,0 +1,59 @@ +const { expect } = require('chai') + +const { + validateUniqueArchIds, + validateImageMatchesFogArch, + imagesAreEqual, + mapYamlImagesToArchList +} = require('../../../src/helpers/arch-images') +const Errors = require('../../../src/helpers/errors') + +describe('arch-images helper', () => { + describe('validateUniqueArchIds', () => { + it('rejects duplicate archId', () => { + expect(() => validateUniqueArchIds([ + { archId: 1, containerImage: 'a' }, + { archId: 1, containerImage: 'b' } + ])).to.throw(Errors.ValidationError, /Duplicate archId/) + }) + }) + + describe('validateImageMatchesFogArch', () => { + it('requires image.archId === fog.archId', () => { + expect(() => validateImageMatchesFogArch('ms-a', { archId: 2 }, [ + { archId: 1, containerImage: 'img' } + ])).to.throw(Errors.ValidationError) + }) + + it('accepts matching archId', () => { + validateImageMatchesFogArch('ms-a', { archId: 2 }, [ + { archId: 2, containerImage: 'img' } + ]) + }) + }) + + describe('imagesAreEqual', () => { + it('compares archId and containerImage regardless of order', () => { + expect(imagesAreEqual( + [{ archId: 2, containerImage: 'arm' }, { archId: 1, containerImage: 'x86' }], + [{ archId: 1, containerImage: 'x86' }, { archId: 2, containerImage: 'arm' }] + )).to.equal(true) + }) + }) + + describe('mapYamlImagesToArchList', () => { + it('maps all supported YAML keys', () => { + expect(mapYamlImagesToArchList({ + amd64: 'a', + arm64: 'b', + riscv: 'c', + arm: 'd' + })).to.eql([ + { archId: 1, containerImage: 'a' }, + { archId: 2, containerImage: 'b' }, + { archId: 3, containerImage: 'c' }, + { archId: 4, containerImage: 'd' } + ]) + }) + }) +}) diff --git a/test/src/services/catalog-service.test.js b/test/src/services/catalog-service.test.js index a23d37a2..65b218cc 100644 --- a/test/src/services/catalog-service.test.js +++ b/test/src/services/catalog-service.test.js @@ -34,11 +34,11 @@ describe('Catalog Service', () => { 'images': [ { 'containerImage': 'x86 docker image name', - 'fogTypeId': 1, + 'archId': 1, }, { 'containerImage': 'ARM docker image name', - 'fogTypeId': 2, + 'archId': 2, }, ], 'publisher': 'string', @@ -59,6 +59,7 @@ describe('Catalog Service', () => { } const catalogItem = { + id: 15, name: data.name, description: data.description, category: data.category, @@ -72,28 +73,11 @@ describe('Catalog Service', () => { userId: user.id, } - const catalogItemImages = [ - { - fogTypeId: 1, - catalogItemId: catalogItem.id, - }, - { - fogTypeId: 2, - catalogItemId: catalogItem.id, - }, - ] - if (data.images) { - for (const image of data.images) { - switch (image.fogTypeId) { - case 1: - catalogItemImages[0].containerImage = image.containerImage - break - case 2: - catalogItemImages[1].containerImage = image.containerImage - break - } - } - } + const catalogItemImages = data.images.map((image) => ({ + archId: image.archId, + catalogItemId: catalogItem.id, + containerImage: image.containerImage + })) const catalogItemInputType = { catalogItemId: catalogItem.id, @@ -318,11 +302,11 @@ describe('Catalog Service', () => { 'images': [ { 'containerImage': 'x86 docker image name', - 'fogTypeId': 1, + 'archId': 1, }, { 'containerImage': 'ARM docker image name', - 'fogTypeId': 2, + 'archId': 2, }, ], 'publisher': 'string', @@ -363,38 +347,16 @@ describe('Catalog Service', () => { registryId: data.registryId, } - const image1 = { - fogTypeId: 1, - catalogItemId: id, - } - const image2 = { - fogTypeId: 2, - catalogItemId: id, - } - const catalogItemImages = [ - image1, image2, - ] - - if (data.images) { - for (const image of data.images) { - switch (image.fogTypeId) { - case 1: - catalogItemImages[0].containerImage = image.containerImage - break - case 2: - catalogItemImages[1].containerImage = image.containerImage - break - } - } - } + const image1 = { archId: 1 } + const image2 = { archId: 2 } const updatedImage1 = { - fogTypeId: 1, + archId: 1, containerImage: 'x86 docker image name', } const updatedImage2 = { - fogTypeId: 2, + archId: 2, containerImage: 'ARM docker image name', } @@ -556,10 +518,10 @@ describe('Catalog Service', () => { await $subject expect(CatalogItemImageManager.updateOrCreate).to.have.been.calledWith({ catalogItemId: data.id, - fogTypeId: image1.fogTypeId, + archId: image1.archId, }, { catalogItemId: data.id, - fogTypeId: image1.fogTypeId, + archId: image1.archId, containerImage: updatedImage1.containerImage, }, transaction) }) @@ -577,10 +539,10 @@ describe('Catalog Service', () => { await $subject expect(CatalogItemImageManager.updateOrCreate).to.have.been.calledWith({ catalogItemId: id, - fogTypeId: image2.fogTypeId, + archId: image2.archId, }, { catalogItemId: id, - fogTypeId: image2.fogTypeId, + archId: image2.archId, containerImage: updatedImage2.containerImage, }, transaction) }) diff --git a/test/src/services/controller-ms-service.test.js b/test/src/services/controller-ms-service.test.js index a3659b71..874ce7c1 100644 --- a/test/src/services/controller-ms-service.test.js +++ b/test/src/services/controller-ms-service.test.js @@ -28,7 +28,7 @@ describe('Controller MS Service', () => { uuid: fogUuid, name: 'system-fog', isSystem: true, - fogTypeId: 1, + archId: 1, availableRuntimes: JSON.stringify(['io.containerd.runc.v2']) } @@ -36,7 +36,7 @@ describe('Controller MS Service', () => { uuid: 'regular-fog-uuid', name: 'regular-fog', isSystem: false, - fogTypeId: 1 + archId: 1 } const application = { @@ -47,7 +47,7 @@ describe('Controller MS Service', () => { const registerData = { uuid: msUuid, - images: [{ containerImage: 'controller:latest', fogTypeId: 1 }], + images: [{ containerImage: 'controller:latest', archId: 1 }], registryId: 1, ports: [{ internal: 8080, external: 8080, protocol: 'tcp' }], volumeMappings: [{ @@ -171,7 +171,7 @@ describe('Controller MS Service', () => { }) CatalogItemImageManager.findAll.resolves([{ containerImage: 'controller:old', - fogTypeId: 1 + archId: 1 }]) }) diff --git a/test/src/services/microservices-service.test.js b/test/src/services/microservices-service.test.js index 76fbc61f..cbb01bdb 100644 --- a/test/src/services/microservices-service.test.js +++ b/test/src/services/microservices-service.test.js @@ -321,7 +321,7 @@ describe('Microservices Service', () => { const fog = { uuid: microserviceData.iofogUuid, - fogTypeId: 1, + archId: 1, name: 'testfog' } @@ -351,8 +351,8 @@ describe('Microservices Service', () => { } const images = [ - {fogTypeId: 1, containerImage: 'hello-world'}, - {fogTypeId: 2, containerImage: 'hello-world'}, + {archId: 1, containerImage: 'hello-world'}, + {archId: 2, containerImage: 'hello-world'}, ] const proxyCatalogItem = { @@ -1018,8 +1018,8 @@ describe('Microservices Service', () => { }; const images = [ - {fogTypeId: 1, containerImage: 'hello-world'}, - {fogTypeId: 2, containerImage: 'hello-world'}, + {archId: 1, containerImage: 'hello-world'}, + {archId: 2, containerImage: 'hello-world'}, ] const newMicroserviceUuid = microserviceUuid; @@ -1058,7 +1058,7 @@ describe('Microservices Service', () => { def('newMicroserviceResponse', () => Promise.resolve(newMicroservice)) def('findRegistryResponse', () => Promise.resolve({})) def('findCatalogItem', () => Promise.resolve({ images })) - def('findFogResponse', () => Promise.resolve({fogTypeId: 1, uuid: microserviceData.iofogUuid })) + def('findFogResponse', () => Promise.resolve({archId: 1, uuid: microserviceData.iofogUuid })) def('findRelatedExtraHostsResponse', () => Promise.resolve([])) def('catalogResponse', () => Promise.resolve(images)) def('updateResponse',() => Promise.resolve()) @@ -1177,7 +1177,7 @@ describe('Microservices Service', () => { targetFogUuid: 'previousUuid', save: () => {} }] - const extraHostFog = {uuid: newMicroservice.iofogUuid, host: '1.2.3.4', fogTypeId: 1} + const extraHostFog = {uuid: newMicroservice.iofogUuid, host: '1.2.3.4', archId: 1} context('when there is no valid image', () => { const catalogItemNoImages = { @@ -1214,7 +1214,7 @@ describe('Microservices Service', () => { } const newFog = { uuid: 'newFogUuid', - fogTypeId: 1 + archId: 1 } const portMappings = [] def('oldMicroserviceResponse', () => Promise.resolve({ @@ -1515,8 +1515,8 @@ describe('Microservices Service', () => { context('when images are updated', () => { const images = [ - {fogTypeId: 1, containerImage: 'newImage:x86'}, - {fogTypeId: 2, containerImage: 'newImage:arm'}, + {archId: 1, containerImage: 'newImage:x86'}, + {archId: 2, containerImage: 'newImage:arm'}, ] registryId = 1 const microserviceUpdateDataWithImages = {...microserviceUpdateData, images, registryId} @@ -1941,7 +1941,7 @@ describe('Microservices Service', () => { def('fog', () => ({ uuid: fogUuid, name: 'agent-1', - fogTypeId: 1, + archId: 1, availableRuntimes: '["docker"]' })) def('microserviceData', () => ({ @@ -1949,7 +1949,7 @@ describe('Microservices Service', () => { application: application.name, iofogUuid: fogUuid, runtime: 'edgelet', - images: [{ fogTypeId: 1, containerImage: 'hello-world' }] + images: [{ archId: 1, containerImage: 'hello-world' }] })) def('subject', () => MicroservicesService.createMicroserviceEndPoint($microserviceData, isCLI, transaction)) @@ -1972,7 +1972,7 @@ describe('Microservices Service', () => { def('fog', () => ({ uuid: fogUuid, name: 'agent-1', - fogTypeId: 1, + archId: 1, availableRuntimes: '' })) @@ -1999,7 +1999,7 @@ describe('Microservices Service', () => { describe('.createMicroserviceEndPoint()', () => { const application = { name: 'my-app', id: 42, active: true } - const fog = { uuid: 'fog-uuid', name: 'agent-1', fogTypeId: 1, availableRuntimes: '["docker"]' } + const fog = { uuid: 'fog-uuid', name: 'agent-1', archId: 1, availableRuntimes: '["docker"]' } def('subject', () => MicroservicesService.createMicroserviceEndPoint($microserviceData, isCLI, transaction)) @@ -2046,7 +2046,7 @@ describe('Microservices Service', () => { name: microserviceName, application: application.name, iofogUuid: fog.uuid, - images: [{ fogTypeId: 1, containerImage: 'hello-world' }], + images: [{ archId: 1, containerImage: 'hello-world' }], volumeMappings: [{ hostDestination: microserviceName, containerDestination: saContainerDestination, @@ -2068,7 +2068,7 @@ describe('Microservices Service', () => { name: microserviceName, application: application.name, iofogUuid: fog.uuid, - images: [{ fogTypeId: 1, containerImage: 'hello-world' }], + images: [{ archId: 1, containerImage: 'hello-world' }], volumeMappings: [{ hostDestination: '/var/dest', containerDestination: '/var/dest', @@ -2161,7 +2161,7 @@ describe('Microservices Service', () => { iofogUuid: 'fog-uuid', schedule: 0, catalogItem: null, - getImages: () => Promise.resolve([{ fogTypeId: 1, containerImage: 'hello-world' }]), + getImages: () => Promise.resolve([{ archId: 1, containerImage: 'hello-world' }]), getPorts: () => Promise.resolve([]) } @@ -2180,7 +2180,7 @@ describe('Microservices Service', () => { $sandbox.stub(CatalogItemImageManager, 'findAll').resolves([]) $sandbox.stub(ApplicationManager, 'findOne').resolves({ id: 42, natsAccess: false }) $sandbox.stub(RegistryManager, 'findOne').resolves({ id: 1 }) - $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid: 'fog-uuid', fogTypeId: 1 }) + $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid: 'fog-uuid', archId: 1 }) $sandbox.stub(MicroserviceExtraHostManager, 'findAll').resolves([]) $sandbox.stub(MicroserviceManager, 'updateAndFind').resolves(microservice) $sandbox.stub(ChangeTrackingService, 'update').resolves() @@ -2209,7 +2209,7 @@ describe('Microservices Service', () => { describe('controller microservice user guards', () => { const transaction = {} const microserviceUuid = 'controller-ms-uuid' - const images = [{ fogTypeId: 1, containerImage: 'controller:latest' }] + const images = [{ archId: 1, containerImage: 'controller:latest' }] const microservice = { uuid: microserviceUuid, name: 'controller', @@ -2235,8 +2235,8 @@ describe('Microservices Service', () => { $sandbox.stub(CatalogItemImageManager, 'findAll').resolves(images) $sandbox.stub(ApplicationManager, 'findOne').resolves({ id: microservice.applicationId, natsAccess: false }) $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((obj) => obj) - $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid: microservice.iofogUuid, fogTypeId: 1 }) - $sandbox.stub(ioFogService, 'getFog').resolves({ uuid: microservice.iofogUuid, fogTypeId: 1 }) + $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid: microservice.iofogUuid, archId: 1 }) + $sandbox.stub(ioFogService, 'getFog').resolves({ uuid: microservice.iofogUuid, archId: 1 }) }) it('blocks user update when isController', async () => { diff --git a/test/src/services/yaml-parser-service.test.js b/test/src/services/yaml-parser-service.test.js index 6297841c..7c6fc0f6 100644 --- a/test/src/services/yaml-parser-service.test.js +++ b/test/src/services/yaml-parser-service.test.js @@ -61,6 +61,29 @@ spec: expect(result.name).to.eql('ms-b') }) + it('maps multi-arch YAML image keys to archId', async () => { + const yaml = ` +kind: Microservice +metadata: + name: app-a/ms-d +spec: + images: + x86: edgeworx/foo:x86 + arm64: edgeworx/foo:arm64 + riscv64: edgeworx/foo:riscv + arm: edgeworx/foo:arm32 + container: + env: [] +` + const result = await YamlParserService.parseMicroserviceFile(yaml) + expect(result.images).to.eql([ + { archId: 1, containerImage: 'edgeworx/foo:x86' }, + { archId: 2, containerImage: 'edgeworx/foo:arm64' }, + { archId: 3, containerImage: 'edgeworx/foo:riscv' }, + { archId: 4, containerImage: 'edgeworx/foo:arm32' } + ]) + }) + it('does not map deprecated top-level natsAccess', async () => { const yaml = ` kind: Microservice From 1b0c2f20759f531a2645064b84aa2b015ac66aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 18:10:58 +0300 Subject: [PATCH 33/75] Use canonical agent config fields across model, API, and CLI. Rename dockerUrl/dockerPruningFrequency to containerEngineUrl/pruningFrequency and default container engine to edgelet. --- scripts/cli-tests.js | 6 +++--- src/cli/iofog.js | 8 ++++---- src/data/models/fog.js | 10 +++++----- src/services/agent-service.js | 8 ++++---- src/services/iofog-service.js | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/scripts/cli-tests.js b/scripts/cli-tests.js index 72bb7c34..00ad07ed 100644 --- a/scripts/cli-tests.js +++ b/scripts/cli-tests.js @@ -107,11 +107,11 @@ function testIoFogSection () { try { const ioFogCreateResponse = responseHasFields(testCommand('iofog add -n ioFog1 -l testLocation -t 55 -g 65' + - ' -d testDescription -D testDockerUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + + ' -d testDescription -D testcontainerEngineUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + ' -s 25 -F 27 -Q 26 -B -W -A -y 1 -u '), ioFogCreateFields) const ioFogUuid = ioFogCreateResponse.uuid responseEquals(testCommand('iofog update -i ' + ioFogUuid + ' -n ioFog1 -l testLocation -t 55 -g 65 ' + - '-d testDescription -D testDockerUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + + '-d testDescription -D testcontainerEngineUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + ' -s 25 -F 27 -Q 26 -B -W -A -y 1 -L INFO -p 65 -k 95 -u '), 'ioFog node has been updated successfully.') responseHasFields(testCommand('iofog list'), ioFogListFields) responseHasFields(testCommand('iofog info -i ' + ioFogUuid), ioFogCreateFields) @@ -193,7 +193,7 @@ function testMicroserviceSection () { const applicationId = applicationCreateResponse.name const ioFogCreateResponse = responseHasFields(executeCommand('iofog add -n ioFog2 -l testLocation -t 55 -g 65 ' + - '-d testDescription -D testDockerUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + + '-d testDescription -D testcontainerEngineUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + ' -s 25 -F 27 -Q 26 -B -W -A -y 1 -u '), ioFogCreateFields) const ioFogUuid = ioFogCreateResponse.uuid diff --git a/src/cli/iofog.js b/src/cli/iofog.js index 123df924..0bff3db5 100644 --- a/src/cli/iofog.js +++ b/src/cli/iofog.js @@ -25,7 +25,7 @@ const JSON_SCHEMA = AppHelper.stringifyCliJsonSchema({ latitude: 0, longitude: 0, description: 'string', - dockerUrl: 'string', + containerEngineUrl: 'string', diskLimit: 0, diskDirectory: 'string', memoryLimit: 0, @@ -40,7 +40,7 @@ const JSON_SCHEMA = AppHelper.stringifyCliJsonSchema({ watchdogEnabled: true, abstractedHardwareEnabled: false, archId: 0, - dockerPruningFrequency: 0, + pruningFrequency: 0, availableDiskThreshold: 0, logLevel: 'string', timeZone: 'string' @@ -477,7 +477,7 @@ function _createFogObject (cliData) { latitude: cliData.latitude, longitude: cliData.longitude, description: cliData.description, - dockerUrl: cliData.dockerUrl, + containerEngineUrl: cliData.containerEngineUrl, diskLimit: cliData.diskLimit, diskDirectory: cliData.diskDirectory, memoryLimit: cliData.memoryLimit, @@ -493,7 +493,7 @@ function _createFogObject (cliData) { abstractedHardwareEnabled: AppHelper.validateBooleanCliOptions(cliData.absHwEnable, cliData.absHwDisable), archId: cliData.archId, - dockerPruningFrequency: cliData.dockerPruningFrequency, + pruningFrequency: cliData.pruningFrequency, availableDiskThreshold: cliData.availableDiskThreshold, logLevel: cliData.logLevel, timeZone: cliData.timeZone diff --git a/src/data/models/fog.js b/src/data/models/fog.js index bc8fcd09..3096b03e 100644 --- a/src/data/models/fog.js +++ b/src/data/models/fog.js @@ -190,16 +190,16 @@ module.exports = (sequelize, DataTypes) => { defaultValue: 'dynamic', field: 'network_interface' }, - dockerUrl: { + containerEngineUrl: { type: DataTypes.TEXT, - defaultValue: 'unix:///var/run/docker.sock', + defaultValue: 'unix:///run/edgelet/contaienrd.sock', field: 'docker_url' }, containerEngine: { - type: DataTypes.ENUM('docker', 'podman'), + type: DataTypes.ENUM('edgelet', 'docker', 'podman'), allowNull: false, field: 'container_engine', - defaultValue: 'docker' + defaultValue: 'edgelet' }, deploymentType: { type: DataTypes.ENUM('native', 'container'), @@ -299,7 +299,7 @@ module.exports = (sequelize, DataTypes) => { defaultValue: 0, field: 'edge_guard_frequency' }, - dockerPruningFrequency: { + pruningFrequency: { type: DataTypes.INTEGER, defaultValue: 0, field: 'docker_pruning_freq' diff --git a/src/services/agent-service.js b/src/services/agent-service.js index 03d426a6..0b422f76 100644 --- a/src/services/agent-service.js +++ b/src/services/agent-service.js @@ -150,7 +150,7 @@ const getAgentConfig = async function (fog, transaction) { }, transaction) const resp = { networkInterface: fogData.networkInterface, - containerEngineUrl: fogData.dockerUrl, + containerEngineUrl: fogData.containerEngineUrl, diskLimit: fogData.diskLimit, diskDirectory: fogData.diskDirectory, memoryLimit: fogData.memoryLimit, @@ -170,7 +170,7 @@ const getAgentConfig = async function (fog, transaction) { longitude: fogData.longitude, logLevel: fogData.logLevel, availableDiskThreshold: fogData.availableDiskThreshold, - pruningFrequency: fogData.dockerPruningFrequency, + pruningFrequency: fogData.pruningFrequency, timeZone: fogData.timeZone } return resp @@ -181,7 +181,7 @@ const updateAgentConfig = async function (updateData, fog, transaction) { let update = { networkInterface: updateData.networkInterface, - dockerUrl: updateData.containerEngineUrl, + containerEngineUrl: updateData.containerEngineUrl, diskLimit: updateData.diskLimit, diskDirectory: updateData.diskDirectory, memoryLimit: updateData.memoryLimit, @@ -199,7 +199,7 @@ const updateAgentConfig = async function (updateData, fog, transaction) { gpsDevice: updateData.gpsDevice, gpsScanFrequency: updateData.gpsScanFrequency, edgeGuardFrequency: updateData.edgeGuardFrequency, - dockerPruningFrequency: updateData.pruningFrequency, + pruningFrequency: updateData.pruningFrequency, availableDiskThreshold: updateData.availableDiskThreshold, logLevel: updateData.logLevel, timeZone: updateData.timeZone diff --git a/src/services/iofog-service.js b/src/services/iofog-service.js index da0b8688..189383e6 100644 --- a/src/services/iofog-service.js +++ b/src/services/iofog-service.js @@ -324,7 +324,7 @@ async function createFogEndPoint (fogData, isCLI, transaction) { // gpsMode: fogData.latitude || fogData.longitude ? 'manual' : undefined, description: fogData.description, networkInterface: fogData.networkInterface, - dockerUrl: fogData.containerEngineUrl, + containerEngineUrl: fogData.containerEngineUrl, containerEngine: fogData.containerEngine, deploymentType: fogData.deploymentType, diskLimit: fogData.diskLimit, @@ -343,7 +343,7 @@ async function createFogEndPoint (fogData, isCLI, transaction) { archId: _resolveArchId(fogData), logLevel: fogData.logLevel, edgeGuardFrequency: fogData.edgeGuardFrequency, - dockerPruningFrequency: fogData.pruningFrequency, + pruningFrequency: fogData.pruningFrequency, availableDiskThreshold: fogData.availableDiskThreshold, isSystem: fogData.isSystem, host: fogData.host, @@ -522,7 +522,7 @@ async function updateFogEndPoint (fogData, isCLI, transaction) { // gpsMode: fogData.latitude || fogData.longitude ? 'manual' : undefined, description: fogData.description, networkInterface: fogData.networkInterface, - dockerUrl: fogData.containerEngineUrl, + containerEngineUrl: fogData.containerEngineUrl, containerEngine: fogData.containerEngine, deploymentType: fogData.deploymentType, diskLimit: fogData.diskLimit, @@ -541,7 +541,7 @@ async function updateFogEndPoint (fogData, isCLI, transaction) { abstractedHardwareEnabled: fogData.abstractedHardwareEnabled, archId: _resolveArchId(fogData), logLevel: fogData.logLevel, - dockerPruningFrequency: fogData.pruningFrequency, + pruningFrequency: fogData.pruningFrequency, edgeGuardFrequency: fogData.edgeGuardFrequency, host: fogData.host, availableDiskThreshold: fogData.availableDiskThreshold, From 340f8d6e717d8248233c368e5e5bfe6d1e98cc11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 18:11:03 +0300 Subject: [PATCH 34/75] Replace keycloak-connect with openid-client and refresh dependencies. Add compare-versions as a direct dependency, bump jsonschema, remove axios and keycloak-connect, and clear stale audit suppressions. --- .nsprc | 5 - package-lock.json | 3568 ++++++++++++++------------------------------- package.json | 6 +- 3 files changed, 1106 insertions(+), 2473 deletions(-) diff --git a/.nsprc b/.nsprc index ace7fe1d..e69de29b 100644 --- a/.nsprc +++ b/.nsprc @@ -1,5 +0,0 @@ -{ - "1112030": { - "notes": "" - } -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c766b486..a5da5dfc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,11 +25,11 @@ "@opentelemetry/instrumentation-http": "^0.218.0", "@opentelemetry/resources": "^1.8.0", "@opentelemetry/sdk-node": "^0.218.0", - "axios": "1.17.0", "bignumber.js": "^9.3.0", "body-parser": "^1.20.4", "command-line-args": "5.2.1", "command-line-usage": "7.0.3", + "compare-versions": "^3.6.0", "concurrent-queue": "7.0.2", "cookie-parser": "1.4.7", "cors": "2.8.5", @@ -44,14 +44,14 @@ "is-elevated": "3.0.0", "jose": "^4.15.9", "js-yaml": "4.1.1", - "jsonschema": "1.4.1", - "keycloak-connect": "^26.1.1", + "jsonschema": "1.5.0", "moment": "2.30.1", "multer": "1.4.5-lts.1", "mysql2": "3.10.1", "nconf": "0.12.1", "node-fetch-npm": "^2.0.4", "node-forge": "^1.4.0", + "openid-client": "^6.8.4", "pg": "8.12.0", "pino": "9.13.1", "pino-std-serializers": "7.0.0", @@ -91,57 +91,33 @@ "node": "^24.0.0" } }, - "node_modules/@aws-crypto/sha256-browser": { + "node_modules/@aws-crypto/crc32": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" } }, "node_modules/@aws-crypto/sha256-js": { @@ -178,88 +154,21 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.1042.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.1042.0.tgz", - "integrity": "sha512-doHP17OwqhcuW3e7fKkFfF4rDFM0hY8IVIwqwvBQRLk6IsZXzpl/YRhQWuIiy9O7BSZgzKKE+XytdElsTd9PWQ==", + "version": "3.1065.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.1065.0.tgz", + "integrity": "sha512-6SngzPglYGapTFrqkiDJwIxPszNoQtEF54+VAoG3/sw3ldf9IR9cGx2bzc18LOC43iI/0YJ2W5X0oaxyCWmDAw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/credential-provider-node": "^3.972.39", - "@aws-sdk/middleware-host-header": "^3.972.10", - "@aws-sdk/middleware-logger": "^3.972.10", - "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.24", - "@smithy/config-resolver": "^4.4.17", - "@smithy/core": "^3.23.17", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/hash-node": "^4.2.14", - "@smithy/invalid-dependency": "^4.2.14", - "@smithy/middleware-content-length": "^4.2.14", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.7", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.49", - "@smithy/util-defaults-mode-node": "^4.2.54", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-utf8": "^4.2.2", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/credential-provider-node": "^3.972.54", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -267,24 +176,18 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.974.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.8.tgz", - "integrity": "sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==", + "version": "3.974.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.20.tgz", + "integrity": "sha512-7sDi2B2N3mc3nf1nz6FyEx/FCrJ1N1QnBmraHHQNabFaeAh2IaOOLml48/rHOD1bICHgTRkbBgNTvUzEr5Z35g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/xml-builder": "^3.972.22", - "@smithy/core": "^3.23.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-utf8": "^4.2.2", + "@aws-sdk/types": "^3.973.12", + "@aws-sdk/xml-builder": "^3.972.29", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/core": "^3.24.6", + "@smithy/signature-v4": "^5.4.6", + "@smithy/types": "^4.14.3", + "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { @@ -292,15 +195,15 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.34.tgz", - "integrity": "sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ==", + "version": "3.972.46", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.46.tgz", + "integrity": "sha512-+GPXVS2srMOlH74S+SmC1gVuP2TvUZ0siuC0onKO93q+udP+M72dmY8wJfVQ5CX9z/9X5A1HHwz5yRIGBtskvQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/types": "^4.14.1", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -308,20 +211,17 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.36.tgz", - "integrity": "sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg==", + "version": "3.972.48", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.48.tgz", + "integrity": "sha512-fA5loSdlocacRxyUXtpoHSMuk5rsIKRDzQYVMnMxjcmFeZshaJlJ8lymy/hYKji6sne/UmNGj5pxuEs6kq/Qcg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/property-provider": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-stream": "^4.5.25", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -329,24 +229,23 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.38.tgz", - "integrity": "sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/credential-provider-env": "^3.972.34", - "@aws-sdk/credential-provider-http": "^3.972.36", - "@aws-sdk/credential-provider-login": "^3.972.38", - "@aws-sdk/credential-provider-process": "^3.972.34", - "@aws-sdk/credential-provider-sso": "^3.972.38", - "@aws-sdk/credential-provider-web-identity": "^3.972.38", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/credential-provider-imds": "^4.2.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", + "version": "3.972.52", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.52.tgz", + "integrity": "sha512-szg1nnebqC+Svv6Vfsdf6P/QK8x5g/ghG2CKa/1WkHifRnq0BBmDELj2Qnqk9nPsUvEu/OEcYic97CPLpKqF9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/credential-provider-env": "^3.972.46", + "@aws-sdk/credential-provider-http": "^3.972.48", + "@aws-sdk/credential-provider-login": "^3.972.51", + "@aws-sdk/credential-provider-process": "^3.972.46", + "@aws-sdk/credential-provider-sso": "^3.972.51", + "@aws-sdk/credential-provider-web-identity": "^3.972.51", + "@aws-sdk/nested-clients": "^3.997.19", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/credential-provider-imds": "^4.3.7", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -354,18 +253,16 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.38.tgz", - "integrity": "sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww==", + "version": "3.972.51", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.51.tgz", + "integrity": "sha512-csHFsH+/VjnI40oqm1l1OqMY4B4kza36DbfcbHcgcbobgjebasqUbTU34xvwUkvtoNGGizbfyMSlMzJWUPv3dQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/nested-clients": "^3.997.19", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -373,22 +270,21 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.39.tgz", - "integrity": "sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.34", - "@aws-sdk/credential-provider-http": "^3.972.36", - "@aws-sdk/credential-provider-ini": "^3.972.38", - "@aws-sdk/credential-provider-process": "^3.972.34", - "@aws-sdk/credential-provider-sso": "^3.972.38", - "@aws-sdk/credential-provider-web-identity": "^3.972.38", - "@aws-sdk/types": "^3.973.8", - "@smithy/credential-provider-imds": "^4.2.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", + "version": "3.972.54", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.54.tgz", + "integrity": "sha512-vinTSQtziNHxi2nqXF+76jr2sO44q88Ind1qFFVaotNgBaC1rcWDjBug8yoE8n0ov33s21xks9WY5XDHH9SENw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.46", + "@aws-sdk/credential-provider-http": "^3.972.48", + "@aws-sdk/credential-provider-ini": "^3.972.52", + "@aws-sdk/credential-provider-process": "^3.972.46", + "@aws-sdk/credential-provider-sso": "^3.972.51", + "@aws-sdk/credential-provider-web-identity": "^3.972.51", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/credential-provider-imds": "^4.3.7", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -396,16 +292,15 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.34.tgz", - "integrity": "sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q==", + "version": "3.972.46", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.46.tgz", + "integrity": "sha512-VUoNFBIjWrUN8NbFiQiuxQEgFjvziAlBRPK+ddh27aj65gk0BYu6bLZnrdrNZwpW6vAihtSUtEMQ1PUJ32QRPA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -413,18 +308,17 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.38.tgz", - "integrity": "sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA==", + "version": "3.972.51", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.51.tgz", + "integrity": "sha512-60qhpQcSDIKIr0AuBlmJezKX0b5nbJPCINiR49N9yJXrEI5tTRwsXVBr0IdSvvsNJyqgiINyoBd++Ed0yvggbw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/token-providers": "3.1041.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/nested-clients": "^3.997.19", + "@aws-sdk/token-providers": "3.1065.0", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -432,106 +326,16 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.38.tgz", - "integrity": "sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.10.tgz", - "integrity": "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.10.tgz", - "integrity": "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.11.tgz", - "integrity": "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.37.tgz", - "integrity": "sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-arn-parser": "^3.972.3", - "@smithy/core": "^3.23.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-stream": "^4.5.25", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.38.tgz", - "integrity": "sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==", + "version": "3.972.51", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.51.tgz", + "integrity": "sha512-0X5eWsUIp8ItRJeJBBrhQAPzc9AQelDetRTVTsycCAISCCzM17R4hs/vFAPeQ0o0B35sciLiqe/Pwmml909cZA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@smithy/core": "^3.23.17", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-retry": "^4.3.6", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/nested-clients": "^3.997.19", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -539,65 +343,20 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.6.tgz", - "integrity": "sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w==", + "version": "3.997.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.19.tgz", + "integrity": "sha512-P2Otgf15GBJMKzG6j5Ddf7w+Kz6z2jvesMy874TD3jlMfDWNK7clJeUd7hgigdeVOotjoUP4emcTWVdS9sfZDw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/middleware-host-header": "^3.972.10", - "@aws-sdk/middleware-logger": "^3.972.10", - "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/signature-v4-multi-region": "^3.996.25", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.24", - "@smithy/config-resolver": "^4.4.17", - "@smithy/core": "^3.23.17", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/hash-node": "^4.2.14", - "@smithy/invalid-dependency": "^4.2.14", - "@smithy/middleware-content-length": "^4.2.14", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.7", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.49", - "@smithy/util-defaults-mode-node": "^4.2.54", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.13.tgz", - "integrity": "sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/config-resolver": "^4.4.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/signature-v4-multi-region": "^3.996.33", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -605,16 +364,14 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.25", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.25.tgz", - "integrity": "sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==", + "version": "3.996.33", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.33.tgz", + "integrity": "sha512-Hn0RThJEbyOZWV2PV9Z4YD3nitGPxybmyU17dSe9b61WOBcKnqS0WTtM3c1zyZq9WnGiyrfi/i+UBPUk7cM8Ug==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.37", - "@aws-sdk/types": "^3.973.8", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/types": "^4.14.1", + "@aws-sdk/types": "^3.973.12", + "@smithy/signature-v4": "^5.4.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -622,17 +379,16 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1041.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1041.0.tgz", - "integrity": "sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==", + "version": "3.1065.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1065.0.tgz", + "integrity": "sha512-qdHQntq82gMqG6Tf8xrgmhJxacaYkxW4PEeDg/ISMVJ84EWe7iD6JyCTgbyox3uNDH6vqEJ8GUiTaXCq307zVw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/nested-clients": "^3.997.19", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -640,40 +396,12 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.973.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", - "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz", - "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", + "version": "3.973.12", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.12.tgz", + "integrity": "sha512-43ajd1NF0RMgX5k0hxCNUyEdrtFUsb2aHT2QvpktSC/2Eyb2Jr/JPVqdp0XIoaHWikZJq5tNWSLO6kB5q2eMCA==", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.8.tgz", - "integrity": "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-endpoints": "^3.4.2", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -681,63 +409,25 @@ } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", - "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.10.tgz", - "integrity": "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==", + "version": "3.965.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.7.tgz", + "integrity": "sha512-M0D6oIpohdNHjc7udzTHEQyot0+0iuA36jc2I9Hps+f/GtKi2HO/pyijQnCnNcwZqLB5+rtn81z3eZK/GyjAmA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/types": "^4.14.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.24", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.24.tgz", - "integrity": "sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/types": "^3.973.8", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.22", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.22.tgz", - "integrity": "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==", + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.29.tgz", + "integrity": "sha512-fk0niuGFxfi8yIJuMVM4mhwObkiQSuwZFj3tAPrLVx64Pk3BkrEIpqjzHKY4hKoEBUD6Jg/S74Zj9jy+5F3DnQ==", "license": "Apache-2.0", "dependencies": { - "@nodable/entities": "2.1.0", - "@smithy/types": "^4.14.1", - "fast-xml-parser": "5.7.2", + "@smithy/types": "^4.14.3", + "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" }, "engines": { @@ -754,9 +444,9 @@ } }, "node_modules/@azure-rest/core-client": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.5.1.tgz", - "integrity": "sha512-EHaOXW0RYDKS5CFffnixdyRPak5ytiCtU7uXDcP/uiY+A6jFRwNGzzJBiznkCzvi5EYpY+YWinieqHb0oY916A==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.6.1.tgz", + "integrity": "sha512-KzI10qnkWTsVS2yRBUdc8NLUJ1rOm+292mYs7Pe9wqAj/jv4bRskVm1l8XkKeVTN0OCQtrU5RG0Yhjbz1Wmg7g==", "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.1.2", @@ -797,9 +487,9 @@ } }, "node_modules/@azure/core-client": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", - "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.2.tgz", + "integrity": "sha512-1D2LpsU7y9xrqKjdIbsB7PlrRePw0xsVV8p+AKTlzITrWmscajryfJCdDJB/oGwvDI5HmRo04eMMADB67uwAwQ==", "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.1.2", @@ -814,22 +504,6 @@ "node": ">=20.0.0" } }, - "node_modules/@azure/core-http-compat": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.3.2.tgz", - "integrity": "sha512-Tf6ltdKzOJEgxZeWLCjMxrxbodB/ZeCbzzA1A2qHbhzAjzjHoBVSUeSl/baT/oHAxhc4qdqVaDKnc2+iE932gw==", - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@azure/core-client": "^1.10.0", - "@azure/core-rest-pipeline": "^1.22.0" - } - }, "node_modules/@azure/core-lro": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", @@ -858,9 +532,9 @@ } }, "node_modules/@azure/core-rest-pipeline": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz", - "integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.24.0.tgz", + "integrity": "sha512-PpLsoDQ3AMmKZ0VU+0GrmqMxgp/sExjlVm4R+nLWngeoEGAzOIPVifaxKGU5gMv+nWELUoHfvrolWD+ZS/nFJg==", "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.1.2", @@ -868,7 +542,7 @@ "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", - "@typespec/ts-http-runtime": "^0.3.0", + "@typespec/ts-http-runtime": "^0.3.4", "tslib": "^2.6.2" }, "engines": { @@ -924,14 +598,14 @@ } }, "node_modules/@azure/keyvault-common": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.0.0.tgz", - "integrity": "sha512-wRLVaroQtOqfg60cxkzUkGKrKMsCP6uYXAOomOIysSMyt1/YM0eUn9LqieAWM8DLcU4+07Fio2YGpPeqUbpP9w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.1.0.tgz", + "integrity": "sha512-aCDidWuKY06LWQ4x7/8TIXK6iRqTaRWRL3t7T+LC+j1b07HtoIsOxP/tU90G4jCSBn5TAyUTCtA4MS/y5Hudaw==", "license": "MIT", "dependencies": { + "@azure-rest/core-client": "^2.3.3", "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.3.0", - "@azure/core-client": "^1.5.0", "@azure/core-rest-pipeline": "^1.8.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.10.0", @@ -939,30 +613,29 @@ "tslib": "^2.2.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@azure/keyvault-secrets": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@azure/keyvault-secrets/-/keyvault-secrets-4.10.0.tgz", - "integrity": "sha512-WvXc3h2hqHL1pMzUU7ANE2RBKoxjK3JQc0YNn6GUFvOWQtf2ZR+sH4/5cZu8zAg62v9qLCduBN7065nHKl+AOA==", + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/@azure/keyvault-secrets/-/keyvault-secrets-4.11.2.tgz", + "integrity": "sha512-ECj/kwZbZlQXj2kfWivSICbKwj6W3chmFhv8qUdauqYnjvZ0hWZBFSsZWux7W2nX3MP49PLUCusXk+hAg3pipg==", "license": "MIT", "dependencies": { "@azure-rest/core-client": "^2.3.3", "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", - "@azure/core-http-compat": "^2.2.0", "@azure/core-lro": "^2.7.2", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.0", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", - "@azure/keyvault-common": "^2.0.0", + "@azure/keyvault-common": "^2.1.0", "@azure/logger": "^1.1.4", "tslib": "^2.8.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@azure/logger": { @@ -979,33 +652,33 @@ } }, "node_modules/@azure/msal-browser": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.9.0.tgz", - "integrity": "sha512-CzE+4PefDSJWj26zU7G1bKchlGRRHMBFreG4tAlGuzyI8hAPiYGobaJvZBgZBf6L63iphX7VH+ityL8VgEQz9Q==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.12.0.tgz", + "integrity": "sha512-eNf2aqx1C6I0yT1GEu5ukblFrmaBXGfe1bivpmlfqvK7giPZvoXLa404C8EfeHVsy6EIryfQuPRzuW1fPxWlHg==", "license": "MIT", "dependencies": { - "@azure/msal-common": "16.5.2" + "@azure/msal-common": "16.7.0" }, "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/msal-common": { - "version": "16.5.2", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.5.2.tgz", - "integrity": "sha512-GkDEL6TYo3HgT3UuqakdgE9PZfc1hMki6+Hwgy1uddb/EauvAKfu85vVhuofRSo22D1xTnWt8Ucwfg4vSCVwvA==", + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.7.0.tgz", + "integrity": "sha512-Jb8Y7pX6KM42SIT7KWP6YbY3+vLbwB5b5m+tpiiOzMU1QeyelQzs9lO8jv1e7/Uj9r7tg7VjPvW4T0KB1jF3UQ==", "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/msal-node": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.1.5.tgz", - "integrity": "sha512-ObTeMoNPmq19X3z40et9Xvs4ZoWVeJg43PZMRLG5iwVL+2nCtAerG3YTDItqPp1CfXNwmCXBbg8jn1DOx65c3g==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.2.3.tgz", + "integrity": "sha512-YYX4TchEVddVBiybKvKhV9QO/q22jgewP+BVxKG7Uh115voPcviGlypbKERDsqQdAiSTJrwi80gcWFjYKdo8+Q==", "license": "MIT", "dependencies": { - "@azure/msal-common": "16.5.2", + "@azure/msal-common": "16.7.0", "jsonwebtoken": "^9.0.0" }, "engines": { @@ -1013,13 +686,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -1028,9 +701,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -1038,21 +711,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -1111,14 +784,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -1128,14 +801,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -1164,10 +837,17 @@ "semver": "bin/semver.js" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -1175,29 +855,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1207,9 +887,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -1217,9 +897,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -1227,9 +907,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -1237,27 +917,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -1267,33 +947,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -1326,14 +1006,14 @@ "license": "MIT" }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1462,9 +1142,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", - "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { @@ -1475,7 +1155,7 @@ "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", - "minimatch": "^3.1.3", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -1569,9 +1249,9 @@ "license": "MIT" }, "node_modules/@google-cloud/secret-manager": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@google-cloud/secret-manager/-/secret-manager-6.1.1.tgz", - "integrity": "sha512-dwSuxJ9RNmAW46FjK1StiNIeOiSHHQs/XIy4VArJ6bBMR+WsIvR+zhPh2pa40aFa9uTty67j38Rl268TVV62EA==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@google-cloud/secret-manager/-/secret-manager-6.1.3.tgz", + "integrity": "sha512-3e/5GLusy1sWBEUvIlJbJpaaUlaf4MeeGQDUBdIVCqWYnn0lNPtbO6ZbJhTPiOkct2yxi8DXWgzWDbCJdDq9Bw==", "license": "Apache-2.0", "dependencies": { "google-gax": "^5.0.0" @@ -1581,9 +1261,9 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", - "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.8.0", @@ -1594,14 +1274,14 @@ } }, "node_modules/@grpc/proto-loader": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", - "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", "license": "Apache-2.0", "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", - "protobufjs": "^7.5.3", + "protobufjs": "^7.5.5", "yargs": "^17.7.2" }, "bin": { @@ -1612,29 +1292,43 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1728,12 +1422,12 @@ } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -1886,9 +1580,9 @@ "license": "BSD-3-Clause" }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", "engines": { @@ -2058,9 +1752,9 @@ } }, "node_modules/@nodable/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.1.tgz", + "integrity": "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==", "funding": [ { "type": "github", @@ -2864,9 +2558,9 @@ } }, "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz", - "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==", + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", "license": "Apache-2.0", "engines": { "node": ">=14" @@ -3128,41 +2822,18 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "deprecated": "Deprecated: no longer maintained and no longer used by Sinon packages. See\n https://github.com/sinonjs/nise/issues/243 for replacement details.", "dev": true, "license": "(Unlicense OR Apache-2.0)" }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.17", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.17.tgz", - "integrity": "sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@smithy/core": { - "version": "3.23.17", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.17.tgz", - "integrity": "sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ==", + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.6.tgz", + "integrity": "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-stream": "^4.5.25", - "@smithy/util-utf8": "^4.2.2", - "@smithy/uuid": "^1.1.2", + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -3170,15 +2841,13 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.14.tgz", - "integrity": "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.8.tgz", + "integrity": "sha512-5cAM+KZC02sTqDt6NaLXyu50M/GNMd1eTzDVR8Lb0BBsVtu7RWHo47VPPEEv1vt3Yub6uzr+M5FHC+GtoT0USg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -3186,542 +2855,97 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.17", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.17.tgz", - "integrity": "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.6.tgz", + "integrity": "sha512-FEwEYJ1jlBKdhe9TPzfghEi1bP55ZeEImlDkEa62bBBYzUcnB6RUCyuiS2mqKt6ZVjUbBgcNhzfIctH+Hevx9g==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/querystring-builder": "^4.2.14", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/hash-node": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.14.tgz", - "integrity": "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.14.tgz", - "integrity": "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==", + "node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", - "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "node_modules/@smithy/node-http-handler": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.7.tgz", + "integrity": "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A==", "license": "Apache-2.0", "dependencies": { + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.14.tgz", - "integrity": "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==", + "node_modules/@smithy/signature-v4": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.6.tgz", + "integrity": "sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.32", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.32.tgz", - "integrity": "sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q==", + "node_modules/@smithy/types": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.3.tgz", + "integrity": "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/middleware-retry": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.7.tgz", - "integrity": "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg==", + "node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/service-error-classification": "^4.3.1", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/uuid": "^1.1.2", + "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@smithy/middleware-serde": { - "version": "4.2.20", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.20.tgz", - "integrity": "sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ==", + "node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", + "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@smithy/middleware-stack": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.14.tgz", - "integrity": "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-config-provider": { - "version": "4.3.14", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.14.tgz", - "integrity": "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.6.1.tgz", - "integrity": "sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/querystring-builder": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/property-provider": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.14.tgz", - "integrity": "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/protocol-http": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.14.tgz", - "integrity": "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.14.tgz", - "integrity": "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "@smithy/util-uri-escape": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-parser": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.14.tgz", - "integrity": "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/service-error-classification": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.3.1.tgz", - "integrity": "sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.9.tgz", - "integrity": "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.14.tgz", - "integrity": "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.2", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-uri-escape": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/smithy-client": { - "version": "4.12.13", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.13.tgz", - "integrity": "sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-stream": "^4.5.25", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.1.tgz", - "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/url-parser": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.14.tgz", - "integrity": "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-base64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", - "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", - "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", - "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", - "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-config-provider": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", - "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.49", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.49.tgz", - "integrity": "sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.54", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.54.tgz", - "integrity": "sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^4.4.17", - "@smithy/credential-provider-imds": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-endpoints": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.4.2.tgz", - "integrity": "sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", - "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-middleware": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.14.tgz", - "integrity": "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-retry": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.8.tgz", - "integrity": "sha512-LUIxbTBi+OpvXpg91poGA6BdyoleMDLnfXjVDqyi2RvZmTveY5loE/FgYUBCR5LU2BThW2SoZRh8dTIIy38IPw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/service-error-classification": "^4.3.1", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-stream": { - "version": "4.5.25", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.25.tgz", - "integrity": "sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", - "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-utf8": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", - "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/uuid": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", - "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@testim/chrome-version": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.4.tgz", - "integrity": "sha512-kIhULpw9TrGYnHp/8VfdcneIcxKnLixmADtukQRtJUmsVlMg0niMkwV0xZmi8hqa57xqilIHjWFA0GKvEjVU5g==", - "license": "MIT", - "optional": true - }, - "node_modules/@tootallnate/once": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-3.0.1.tgz", - "integrity": "sha512-VyMVKRrpHTT8PnotUeV8L/mDaMwD5DaAKCFLP73zAqAtvF0FCqky+Ki7BYbFCYQmqFyTe9316Ed5zS70QUR9eg==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "license": "MIT", - "optional": true - }, "node_modules/@types/chai": { "version": "4.3.20", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", @@ -3737,18 +2961,18 @@ "license": "MIT" }, "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { "@types/ms": "*" } }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -3772,12 +2996,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.10.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", - "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.1.tgz", + "integrity": "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/node-fetch": { @@ -3816,20 +3040,10 @@ "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", "license": "MIT" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typespec/ts-http-runtime": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.3.tgz", - "integrity": "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.6.tgz", + "integrity": "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og==", "license": "MIT", "dependencies": { "http-proxy-agent": "^7.0.0", @@ -3927,9 +3141,9 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -4011,6 +3225,18 @@ "node": ">= 8" } }, + "node_modules/anynum": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.0.tgz", + "integrity": "sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -4098,6 +3324,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", @@ -4136,18 +3381,6 @@ "safer-buffer": "~2.1.0" } }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "license": "MIT", - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -4161,24 +3394,11 @@ "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.0.1" - }, "engines": { - "node": ">=4" + "node": ">=12" } }, "node_modules/async": { @@ -4255,79 +3475,10 @@ "dev": true, "license": "MIT" }, - "node_modules/axios": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz", - "integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "https-proxy-agent": "^5.0.1", - "proxy-from-env": "^2.1.0" - } - }, - "node_modules/axios/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/axios/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/axios/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/axios/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/axios/node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/b4a": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", - "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", "license": "Apache-2.0", "peerDependencies": { "react-native-b4a": "*" @@ -4437,9 +3588,9 @@ } }, "node_modules/bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", "license": "Apache-2.0", "peerDependencies": { "bare-abort-controller": "*" @@ -4451,11 +3602,10 @@ } }, "node_modules/bare-fs": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", - "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.2.tgz", + "integrity": "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==", "license": "Apache-2.0", - "optional": true, "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", @@ -4476,40 +3626,41 @@ } }, "node_modules/bare-os": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", - "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", + "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", "license": "Apache-2.0", - "optional": true, "engines": { "bare": ">=1.14.0" } }, "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.1.tgz", + "integrity": "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==", "license": "Apache-2.0", - "optional": true, "dependencies": { "bare-os": "^3.0.1" } }, "node_modules/bare-stream": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.0.tgz", - "integrity": "sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", + "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", "license": "Apache-2.0", - "optional": true, "dependencies": { - "streamx": "^2.21.0", + "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { + "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, "bare-buffer": { "optional": true }, @@ -4519,11 +3670,10 @@ } }, "node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz", + "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==", "license": "Apache-2.0", - "optional": true, "dependencies": { "bare-path": "^3.0.0" } @@ -4549,9 +3699,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "version": "2.10.35", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.35.tgz", + "integrity": "sha512-honAfLBde0HAFLdNyBEfuuENkF6zR+ozxqxa/2zJKHBe1qzLqyTSeRKpdPEHAP03rlDGyQOPnCSxnVpVqQo9Mg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4561,16 +3711,6 @@ "node": ">=6.0.0" } }, - "node_modules/basic-ftp": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", - "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -4687,12 +3827,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bn.js": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", - "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", - "license": "MIT" - }, "node_modules/body-parser": { "version": "1.20.5", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", @@ -4732,9 +3866,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -4756,12 +3890,6 @@ "node": ">=8" } }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "license": "MIT" - }, "node_modules/brotli": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", @@ -4780,9 +3908,9 @@ "license": "ISC" }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -4800,11 +3928,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -4837,16 +3965,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": "*" - } - }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -4911,15 +4029,15 @@ } }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -5002,9 +4120,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001774", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", - "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", + "version": "1.0.30001797", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz", + "integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==", "dev": true, "funding": [ { @@ -5211,41 +4329,14 @@ } }, "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/chromedriver": { - "version": "145.0.6", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-145.0.6.tgz", - "integrity": "sha512-qobFdfjk7G7U9GKB6RYGBuqQ8L0QG1M30p90sNIWLKdpeobhsedfBhVxRqT4m/nWAtM0PhNb9GDD9qzDwSSGlA==", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@testim/chrome-version": "^1.1.4", - "axios": "^1.13.5", - "compare-versions": "^6.1.0", - "extract-zip": "^2.0.1", - "proxy-agent": "^6.4.0", - "proxy-from-env": "^2.0.0", - "tcp-port-used": "^1.0.2" - }, - "bin": { - "chromedriver": "bin/chromedriver" - }, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=20" + "node": ">=18" } }, - "node_modules/chromedriver/node_modules/proxy-from-env": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.0.0.tgz", - "integrity": "sha512-h2lD3OfRraP3R51rNFKIE8nX+qoLr1mE74X91YhVxtDbt+OD6ntoNZv56+JgI4RCdtwQ5eexsOk1KdOQDfvPCQ==", - "license": "MIT", - "optional": true - }, "node_modules/circular-json": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", @@ -5421,9 +4512,9 @@ } }, "node_modules/command-line-usage/node_modules/array-back": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", - "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.3.tgz", + "integrity": "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw==", "license": "MIT", "engines": { "node": ">=12.17" @@ -5456,11 +4547,10 @@ "license": "MIT" }, "node_modules/compare-versions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", - "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", - "license": "MIT", - "optional": true + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", + "license": "MIT" }, "node_modules/component-emitter": { "version": "1.3.1", @@ -5715,13 +4805,12 @@ } }, "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "license": "MIT", - "optional": true, "engines": { - "node": ">= 14" + "node": ">= 12" } }, "node_modules/data-view-buffer": { @@ -5845,7 +4934,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/default-browser": { @@ -5949,21 +5038,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/deglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/deglob/-/deglob-2.1.1.tgz", @@ -6093,25 +5167,18 @@ } }, "node_modules/doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha512-lsGyRuYr4/PIB0txi+Fy2xOMI2dGaTguCaotzFGkVZuKR5usKfcRWIFKNM3QNrU7hh/+w2bwTW+ZeXPK5l8uVg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" + "esutils": "^2.0.2" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/doctrine/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -6128,6 +5195,7 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.7.tgz", "integrity": "sha512-7lAK2A0b3zZr3UC5aE69CPdCFR4RHW1o2Dr74TqFykxkUCBXSRJum/yPc7g8zRHJqWKomPLHwFLLoUnn8PXXRg==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "license": "MIT" }, "node_modules/dunder-proto": { @@ -6206,15 +5274,15 @@ } }, "node_modules/editorconfig": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", - "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz", + "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==", "dev": true, "license": "MIT", "dependencies": { "@one-ini/wasm": "0.1.1", "commander": "^10.0.0", - "minimatch": "9.0.1", + "minimatch": "^9.0.1", "semver": "^7.5.3" }, "bin": { @@ -6256,27 +5324,12 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.302", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", - "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "version": "1.5.371", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.371.tgz", + "integrity": "sha512-e9htk9mAYL6AzmkEhSvVVw7IWGSBJ/Bqdn2eRyRLrj1g6sncN4WbFt5qnILYoCktktr45pyjIrOiRvBThQ808w==", "dev": true, "license": "ISC" }, - "node_modules/elliptic": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", - "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", - "license": "MIT", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -6352,9 +5405,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -6439,9 +5492,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -6465,6 +5518,19 @@ "node": ">= 0.4" } }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-to-primitive": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", @@ -6574,28 +5640,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "license": "BSD-2-Clause", - "optional": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, "node_modules/eslint": { "version": "9.28.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", @@ -6670,41 +5714,16 @@ "eslint": ">=5.16.0" } }, - "node_modules/eslint-config-standard": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz", - "integrity": "sha512-COUz8FnXhqFitYj4DTqHzidjIL/t4mumGZto5c7DrBpvWoie+Sn3P4sLEzUGeYhRElWuFEf8K1S1EfvD1vixCQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=5.0.0", - "eslint-plugin-import": ">=2.13.0", - "eslint-plugin-node": ">=7.0.0", - "eslint-plugin-promise": ">=4.0.0", - "eslint-plugin-standard": ">=4.0.0" - } - }, - "node_modules/eslint-config-standard-jsx": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-6.0.2.tgz", - "integrity": "sha512-D+YWAoXw+2GIdbMBRAzWwr1ZtvnSf4n4yL0gKGg7ShUOGXkSOLerI17K4F6LdQMJPNMoWYqepzQD/fKY+tXNSg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=5.0.0", - "eslint-plugin-react": ">=7.11.1" - } - }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", "dev": true, "license": "MIT", "dependencies": { "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { @@ -6724,10 +5743,34 @@ "dev": true, "license": "MIT" }, + "node_modules/eslint-import-resolver-node/node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/eslint-module-utils": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", - "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.13.0.tgz", + "integrity": "sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6760,45 +5803,20 @@ "license": "MIT" }, "node_modules/eslint-plugin-es": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz", - "integrity": "sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-utils": "^1.4.2", - "regexpp": "^2.0.1" - }, - "engines": { - "node": ">=6.5.0" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", - "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz", + "integrity": "sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA==", "dev": true, "license": "MIT", "dependencies": { - "contains-path": "^0.1.0", - "debug": "^2.6.8", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.1", - "eslint-module-utils": "^2.2.0", - "has": "^1.0.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.3", - "read-pkg-up": "^2.0.0", - "resolve": "^1.6.0" + "eslint-utils": "^1.4.2", + "regexpp": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=6.5.0" }, "peerDependencies": { - "eslint": "2.x - 5.x" + "eslint": ">=4.19.1" } }, "node_modules/eslint-plugin-node": { @@ -6852,39 +5870,6 @@ "node": ">=6" } }, - "node_modules/eslint-plugin-react": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz", - "integrity": "sha512-cVVyMadRyW7qsIUh3FHp3u6QHNhOgVrLQYdQEB1bPWBsgbNCHdFAeNMquBMCcZJu59eNthX053L70l7gRt4SCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.0.3", - "doctrine": "^2.1.0", - "has": "^1.0.3", - "jsx-ast-utils": "^2.0.1", - "prop-types": "^15.6.2" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint-plugin-standard": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.2.tgz", @@ -7038,7 +6023,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -7078,7 +6063,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -7088,7 +6073,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -7357,68 +6342,6 @@ "dev": true, "license": "MIT" }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "license": "BSD-2-Clause", - "optional": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extract-zip/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "license": "MIT", - "optional": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/extract-zip/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -7464,9 +6387,9 @@ "license": "MIT" }, "node_modules/fast-xml-builder": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.8.tgz", - "integrity": "sha512-sDVBc2gg8pSKvcbE8rBmOyjSGQf0AdsbqvHeIOv3D/uYNoV4eCReQXyDF8Pdv8+m1FHazACypSz2hR7O2S1LLw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", "funding": [ { "type": "github", @@ -7475,13 +6398,14 @@ ], "license": "MIT", "dependencies": { - "path-expression-matcher": "^1.1.3" + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" } }, "node_modules/fast-xml-parser": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.2.tgz", - "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", + "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", "funding": [ { "type": "github", @@ -7491,7 +6415,7 @@ "license": "MIT", "dependencies": { "@nodable/entities": "^2.1.0", - "fast-xml-builder": "^1.1.5", + "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, @@ -7499,16 +6423,6 @@ "fxparser": "src/cli/cli.js" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "license": "MIT", - "optional": true, - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -7585,15 +6499,12 @@ "license": "MIT" }, "node_modules/filelist": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.5.tgz", - "integrity": "sha512-ct/ckWBV/9Dg3MlvCXsLcSUyoWwv9mCKqlhLNB2DAuXR/NZolSXlQqP5dyy6guWlPXBhodZyZ5lGPQcbQDxrEQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", "license": "Apache-2.0", "dependencies": { - "minimatch": "^10.2.1" - }, - "engines": { - "node": "20 || >=22" + "minimatch": "^5.0.1" } }, "node_modules/filesize": { @@ -7716,32 +6627,12 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -7995,29 +6886,19 @@ } }, "node_modules/gaxios": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", - "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.5.tgz", + "integrity": "sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2", - "rimraf": "^5.0.1" + "node-fetch": "^3.3.2" }, "engines": { "node": ">=18" } }, - "node_modules/gaxios/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/gaxios/node_modules/node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", @@ -8037,19 +6918,52 @@ } }, "node_modules/gcp-metadata": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.3.tgz", + "integrity": "sha512-ziTrzUhhpL9Zk5k0HHzgP/KIpWDJT0VMBC/ynt/QIBvTW+UUcSivQRl6VlwTf/EilDxtSWklHoRsKy1c4k+59w==", "license": "Apache-2.0", "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", + "gaxios": "7.1.3", + "google-logging-utils": "1.1.3", "json-bigint": "^1.0.0" }, "engines": { "node": ">=18" } }, + "node_modules/gcp-metadata/node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -8185,46 +7099,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "license": "MIT", - "optional": true, - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/get-uri/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/get-uri/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -8342,36 +7216,27 @@ } }, "node_modules/google-gax": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-5.0.6.tgz", - "integrity": "sha512-1kGbqVQBZPAAu4+/R1XxPQKP0ydbNYoLAr4l0ZO2bMV0kLyLW4I1gAk++qBLWt7DPORTzmWRMsCZe86gDjShJA==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-5.0.7.tgz", + "integrity": "sha512-EhiqaWWJ+9h7sCcKJTsoo6tMcjokVHhWsbSuWCnZJT4vIBP3y4mAoFLnt9SzgkVZeq24ZsFaArr06nnYYku2yA==", "license": "Apache-2.0", "dependencies": { "@grpc/grpc-js": "^1.12.6", "@grpc/proto-loader": "^0.8.0", "duplexify": "^4.1.3", - "google-auth-library": "^10.1.0", - "google-logging-utils": "^1.1.1", + "google-auth-library": "10.5.0", + "google-logging-utils": "1.1.3", "node-fetch": "^3.3.2", "object-hash": "^3.0.0", - "proto3-json-serializer": "^3.0.0", - "protobufjs": "^7.5.3", - "retry-request": "^8.0.0", + "proto3-json-serializer": "3.0.4", + "protobufjs": "^7.5.4", + "retry-request": "^8.0.2", "rimraf": "^5.0.1" }, "engines": { "node": ">=18" } }, - "node_modules/google-gax/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/google-gax/node_modules/node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", @@ -8589,16 +7454,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, "node_modules/hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -8630,9 +7485,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -8660,17 +7515,6 @@ "node": ">=16.0.0" } }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "license": "MIT", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -8910,9 +7754,9 @@ } }, "node_modules/import-in-the-middle": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.1.tgz", - "integrity": "sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.2.tgz", + "integrity": "sha512-LGLYRl0A2gtyUJb2WDliBHmk6TtlHwdDjxonacZ8QrEs/ZW+YDgNv2QAfjRQWpS8HqvNcq6GGnN6jrOa5FysDQ==", "license": "Apache-2.0", "dependencies": { "acorn": "^8.15.0", @@ -9309,13 +8153,13 @@ } }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -9715,13 +8559,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "license": "MIT", - "optional": true - }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -9793,31 +8630,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is2": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", - "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", - "license": "MIT", - "optional": true, - "dependencies": { - "deep-is": "^0.1.3", - "ip-regex": "^4.1.0", - "is-url": "^1.2.4" - }, - "engines": { - "node": ">=v0.10.0" - } - }, - "node_modules/is2/node_modules/ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -9952,16 +8764,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/istanbul-lib-processinfo/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -10163,14 +8965,11 @@ } }, "node_modules/js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } + "license": "MIT" }, "node_modules/js-md4": { "version": "0.3.2", @@ -10194,9 +8993,19 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -10298,9 +9107,9 @@ } }, "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10339,9 +9148,9 @@ } }, "node_modules/jsonschema": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", - "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.5.0.tgz", + "integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==", "license": "MIT", "engines": { "node": "*" @@ -10423,17 +9232,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/jwk-to-pem": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-2.0.7.tgz", - "integrity": "sha512-cSVphrmWr6reVchuKQZdfSs4U9c5Y4hwZggPoz6cbVnTpAVgGRpEuQng86IyqLeGZlhTh+c4MAreB6KbdQDKHQ==", - "license": "Apache-2.0", - "dependencies": { - "asn1.js": "^5.3.0", - "elliptic": "^6.6.1", - "safe-buffer": "^5.0.1" - } - }, "node_modules/jws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", @@ -10444,21 +9242,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/keycloak-connect": { - "version": "26.1.1", - "resolved": "https://registry.npmjs.org/keycloak-connect/-/keycloak-connect-26.1.1.tgz", - "integrity": "sha512-2wvNJXldB9Em+mp6liJ+AnftcJovFEvNhUgv3hblNDmVihBoBqn4zFlwLIN41lo0H8CicB2T86xZ5U2MiQ9FFA==", - "license": "Apache-2.0", - "dependencies": { - "jwk-to-pem": "^2.0.0" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "chromedriver": "latest" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -10504,15 +9287,15 @@ } }, "node_modules/load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", + "parse-json": "^4.0.0", + "pify": "^3.0.0", "strip-bom": "^3.0.0" }, "engines": { @@ -10882,14 +9665,9 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, "license": "ISC" }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "license": "MIT" - }, "node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -11303,16 +10081,6 @@ "dev": true, "license": "MIT" }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/newman": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/newman/-/newman-6.2.2.tgz", @@ -11432,9 +10200,9 @@ "license": "MIT" }, "node_modules/node-abi": { - "version": "3.87.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", - "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "version": "3.92.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -11472,6 +10240,35 @@ "node": ">=10.5.0" } }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -11614,11 +10411,14 @@ } }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/nopt": { "version": "7.2.1", @@ -11919,9 +10719,9 @@ } }, "node_modules/oauth4webapi": { - "version": "3.8.5", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.5.tgz", - "integrity": "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==", + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.6.tgz", + "integrity": "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -11988,6 +10788,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/on-error": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/on-error/-/on-error-2.1.0.tgz", @@ -12065,22 +10881,22 @@ } }, "node_modules/openid-client": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.2.tgz", - "integrity": "sha512-uOvTCndr4udZsKihJ68H9bUICrriHdUVJ6Az+4Ns6cW55rwM5h0bjVIzDz2SxgOI84LKjFyjOFvERLzdTUROGA==", + "version": "6.8.4", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.4.tgz", + "integrity": "sha512-QSw0BA08piujetEwfZsHoTrDpMEha7GDZDicQqVwX4u0ChCjefvjDB++TZ8BTg76UpwhzIQgdvvfgfl3HpCSAw==", "license": "MIT", "dependencies": { - "jose": "^6.1.3", - "oauth4webapi": "^3.8.4" + "jose": "^6.2.2", + "oauth4webapi": "^3.8.5" }, "funding": { "url": "https://github.com/sponsors/panva" } }, "node_modules/openid-client/node_modules/jose": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -12196,65 +11012,6 @@ "node": ">=6" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/pac-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "license": "MIT", - "optional": true, - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/package-hash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", @@ -12291,16 +11048,17 @@ } }, "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, "license": "MIT", "dependencies": { - "error-ex": "^1.2.0" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, "node_modules/parse-ms": { @@ -12421,6 +11179,16 @@ "node": ">=4" } }, + "node_modules/path-type/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pathval": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", @@ -12431,13 +11199,6 @@ "node": ">= 14.16" } }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "license": "MIT", - "optional": true - }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -12473,16 +11234,16 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", - "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", "license": "MIT", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz", - "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", + "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==", "license": "MIT" }, "node_modules/pg-int8": { @@ -12495,18 +11256,18 @@ } }, "node_modules/pg-pool": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.11.0.tgz", - "integrity": "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", "license": "MIT", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz", - "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", + "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==", "license": "MIT" }, "node_modules/pg-types": { @@ -12541,9 +11302,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -12554,13 +11315,13 @@ } }, "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, "node_modules/pino": { @@ -12627,22 +11388,6 @@ "node": ">=4" } }, - "node_modules/pkg-conf/node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/pkg-conf/node_modules/locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", @@ -12693,20 +11438,6 @@ "node": ">=4" } }, - "node_modules/pkg-conf/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/pkg-conf/node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -12717,26 +11448,6 @@ "node": ">=4" } }, - "node_modules/pkg-conf/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-conf/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/pkg-config": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pkg-config/-/pkg-config-1.1.1.tgz", @@ -13034,16 +11745,6 @@ "node": ">=10" } }, - "node_modules/postman-collection/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/postman-collection/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -13092,21 +11793,11 @@ "dependencies": { "side-channel": "^1.1.0" }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/postman-request/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/postman-runtime": { @@ -13233,16 +11924,6 @@ "node": ">=0.6" } }, - "node_modules/postman-runtime/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/postman-sandbox": { "version": "4.7.1", "resolved": "https://registry.npmjs.org/postman-sandbox/-/postman-sandbox-4.7.1.tgz", @@ -13299,6 +11980,12 @@ "node": ">=10" } }, + "node_modules/prebuild-install/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, "node_modules/prebuild-install/node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -13515,68 +12202,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "license": "ISC", - "optional": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT", - "optional": true - }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -13591,9 +12216,9 @@ } }, "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -13841,6 +12466,55 @@ "node": ">=4" } }, + "node_modules/read-pkg/node_modules/load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", @@ -14026,12 +12700,13 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "dev": true, "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" @@ -14077,9 +12752,9 @@ "license": "MIT" }, "node_modules/retry-request": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-8.0.2.tgz", - "integrity": "sha512-JzFPAfklk1kjR1w76f0QOIhoDkNkSqW8wYKT08n9yysTmZfB+RQ2QoXoTAeOi1HD9ZipTyTAZg3c4pM/jeqgSw==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-8.0.3.tgz", + "integrity": "sha512-qqoc4kkGgP9cmQDWELlOpAmfgJOg0Yi7MT82ZjiPWu451ayju4itwomjM4/dBEliify8C1b3tSaeCOldugtwPQ==", "license": "MIT", "dependencies": { "extend": "^3.0.2", @@ -14096,9 +12771,9 @@ "license": "MIT" }, "node_modules/rhea": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/rhea/-/rhea-3.0.4.tgz", - "integrity": "sha512-n3kw8syCdrsfJ72w3rohpoHHlmv/RZZEP9VY5BVjjo0sEGIt4YSKypBgaiA+OUSgJAzLjOECYecsclG5xbYtZw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/rhea/-/rhea-3.0.5.tgz", + "integrity": "sha512-Ye1gzqH9DoCGMTaSfLfGmDdoderMshyVU5rdFES+D3N1U5PASZcNCKqhWJJUOOj8INtdrIPOfDALrRcoRRL7jw==", "license": "Apache-2.0", "dependencies": { "debug": "^4.3.3" @@ -14269,15 +12944,15 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, @@ -14379,9 +13054,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -14580,15 +13255,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/sequelize/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/serialised-error": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/serialised-error/-/serialised-error-1.1.3.tgz", @@ -14615,7 +13281,7 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", "dev": true, "license": "MIT", "bin": { @@ -14766,14 +13432,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -14785,13 +13451,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -14958,9 +13624,9 @@ } }, "node_modules/snyk": { - "version": "1.1302.1", - "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.1302.1.tgz", - "integrity": "sha512-RT85Pz4N36xma7Mcob0Jno5TXu22VbVoT+7mWHk2TVkBHqsMZ8sKW55X+r+LMaT8GwlC5+cYjzw2iuv1VcPZOg==", + "version": "1.1305.1", + "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.1305.1.tgz", + "integrity": "sha512-yvQdjZ6wvKfkUXJ1iqiurcUvwWy7ICJSp+kqpC1JuYQ617PO/fSDEO5AkuvkFxOXuVPqUoG6hNaOzrpkO0QWzg==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -14976,12 +13642,12 @@ } }, "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", "license": "MIT", "dependencies": { - "ip-address": "^10.0.1", + "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -15039,7 +13705,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -15364,91 +14030,151 @@ "node": ">=4.8" } }, - "node_modules/standard/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/standard/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/standard/node_modules/eslint": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.4.0.tgz", + "integrity": "sha512-UIpL91XGex3qtL6qwyCQJar2j3osKxK9e3ano3OcGEIRM4oWIpCkDg9x95AXEC2wMs7PnxzOkPZ2gq+tsMS9yg==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "ajv": "^6.5.0", + "babel-code-frame": "^6.26.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^4.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.2", + "imurmurhash": "^0.1.4", + "inquirer": "^5.2.0", + "is-resolvable": "^1.1.0", + "js-yaml": "^3.11.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^2.0.0", + "require-uncached": "^1.0.3", + "semver": "^5.5.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^4.0.3", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^6.14.0 || ^8.10.0 || >=9.10.0" } }, - "node_modules/standard/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/standard/node_modules/eslint-config-standard": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz", + "integrity": "sha512-COUz8FnXhqFitYj4DTqHzidjIL/t4mumGZto5c7DrBpvWoie+Sn3P4sLEzUGeYhRElWuFEf8K1S1EfvD1vixCQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=5.0.0", + "eslint-plugin-import": ">=2.13.0", + "eslint-plugin-node": ">=7.0.0", + "eslint-plugin-promise": ">=4.0.0", + "eslint-plugin-standard": ">=4.0.0" + } + }, + "node_modules/standard/node_modules/eslint-config-standard-jsx": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-6.0.2.tgz", + "integrity": "sha512-D+YWAoXw+2GIdbMBRAzWwr1ZtvnSf4n4yL0gKGg7ShUOGXkSOLerI17K4F6LdQMJPNMoWYqepzQD/fKY+tXNSg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=5.0.0", + "eslint-plugin-react": ">=7.11.1" + } + }, + "node_modules/standard/node_modules/eslint-plugin-import": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", + "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "contains-path": "^0.1.0", + "debug": "^2.6.8", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.1", + "eslint-module-utils": "^2.2.0", + "has": "^1.0.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.3", + "read-pkg-up": "^2.0.0", + "resolve": "^1.6.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "2.x - 5.x" + } + }, + "node_modules/standard/node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha512-lsGyRuYr4/PIB0txi+Fy2xOMI2dGaTguCaotzFGkVZuKR5usKfcRWIFKNM3QNrU7hh/+w2bwTW+ZeXPK5l8uVg==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "esutils": "^2.0.2" + "esutils": "^2.0.2", + "isarray": "^1.0.0" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/standard/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/standard/node_modules/eslint": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.4.0.tgz", - "integrity": "sha512-UIpL91XGex3qtL6qwyCQJar2j3osKxK9e3ano3OcGEIRM4oWIpCkDg9x95AXEC2wMs7PnxzOkPZ2gq+tsMS9yg==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "node_modules/standard/node_modules/eslint-plugin-react": { + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz", + "integrity": "sha512-cVVyMadRyW7qsIUh3FHp3u6QHNhOgVrLQYdQEB1bPWBsgbNCHdFAeNMquBMCcZJu59eNthX053L70l7gRt4SCw==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.5.0", - "babel-code-frame": "^6.26.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^3.1.0", + "array-includes": "^3.0.3", "doctrine": "^2.1.0", - "eslint-scope": "^4.0.0", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^4.0.0", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.2", - "imurmurhash": "^0.1.4", - "inquirer": "^5.2.0", - "is-resolvable": "^1.1.0", - "js-yaml": "^3.11.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.5", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", - "progress": "^2.0.0", - "regexpp": "^2.0.0", - "require-uncached": "^1.0.3", - "semver": "^5.5.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^4.0.3", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1", + "prop-types": "^15.6.2" }, "engines": { - "node": "^6.14.0 || ^8.10.0 || >=9.10.0" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0" } }, "node_modules/standard/node_modules/eslint-scope": { @@ -15475,6 +14201,16 @@ "node": ">=4" } }, + "node_modules/standard/node_modules/eslint/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/standard/node_modules/espree": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/espree/-/espree-4.1.0.tgz", @@ -15582,6 +14318,13 @@ "node": ">= 4" } }, + "node_modules/standard/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/standard/node_modules/js-yaml": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", @@ -15849,9 +14592,9 @@ } }, "node_modules/streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.27.0.tgz", + "integrity": "sha512-WZ189TKnHoAokYHvwzaAQMpd55cgUmFIcJFzBSgGcb886jau5DL+XdDhTWV4ps3FLvk+OORp0dLRTPsLZ21CSA==", "license": "MIT", "dependencies": { "events-universal": "^1.0.0", @@ -15901,19 +14644,20 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -15923,16 +14667,16 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.2" }, "engines": { "node": ">= 0.4" @@ -16017,16 +14761,19 @@ } }, "node_modules/strnum": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", - "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.0.tgz", + "integrity": "sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "anynum": "^1.0.0" + } }, "node_modules/stubs": { "version": "3.0.0", @@ -16188,9 +14935,9 @@ } }, "node_modules/table-layout/node_modules/array-back": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", - "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.3.tgz", + "integrity": "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw==", "license": "MIT", "engines": { "node": ">=12.17" @@ -16338,9 +15085,9 @@ } }, "node_modules/tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", "license": "MIT", "dependencies": { "pump": "^3.0.0", @@ -16352,78 +15099,25 @@ } }, "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", "license": "MIT", "dependencies": { "b4a": "^1.6.4", + "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, - "node_modules/tar/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/tcp-port-used": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", - "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4.3.1", - "is2": "^2.0.6" - } - }, - "node_modules/tcp-port-used/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/tcp-port-used/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT", - "optional": true - }, "node_modules/teeny-request": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.0.tgz", - "integrity": "sha512-3ZnLvgWF29jikg1sAQ1g0o+lr5JX6sVgYvfUJazn7ZjJroDBUTWp44/+cFVX0bULjv4vci+rBD+oGVAkWqhUbw==", + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.3.tgz", + "integrity": "sha512-5yDliI1uWkYPo7W+Zvrxg6YmoWuj5iC5EydewqrRTvc68nyMTZhlPPlLg6cptUGfbQAb+N9XDPDPzF6N081lug==", "license": "Apache-2.0", "dependencies": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "stream-events": "^1.0.5" }, @@ -16431,77 +15125,6 @@ "node": ">=18" } }, - "node_modules/teeny-request/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/teeny-request/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/teeny-request/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/teeny-request/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "license": "MIT", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/teeny-request/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/teeny-request/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/teeny-request/node_modules/node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", @@ -16525,7 +15148,6 @@ "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", "license": "MIT", - "optional": true, "dependencies": { "streamx": "^2.12.5" } @@ -16591,9 +15213,9 @@ "license": "MIT" }, "node_modules/thread-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz", + "integrity": "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==", "license": "MIT", "dependencies": { "real-require": "^0.2.0" @@ -16850,18 +15472,18 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" }, "engines": { "node": ">= 0.4" @@ -16978,9 +15600,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/uniq": { @@ -17076,6 +15698,16 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/uvm": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/uvm/-/uvm-2.1.1.tgz", @@ -17108,9 +15740,9 @@ } }, "node_modules/validator": { - "version": "13.15.26", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", - "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -17269,14 +15901,14 @@ "license": "ISC" }, "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", @@ -17455,6 +16087,21 @@ "dev": true, "license": "MIT" }, + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", @@ -17508,11 +16155,13 @@ } }, "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/yaml": { "version": "2.9.0", @@ -17607,17 +16256,6 @@ "node": ">=12" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "license": "MIT", - "optional": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index c6838fd6..0515300a 100644 --- a/package.json +++ b/package.json @@ -75,11 +75,11 @@ "@opentelemetry/instrumentation-http": "^0.218.0", "@opentelemetry/resources": "^1.8.0", "@opentelemetry/sdk-node": "^0.218.0", - "axios": "1.17.0", "bignumber.js": "^9.3.0", "body-parser": "^1.20.4", "command-line-args": "5.2.1", "command-line-usage": "7.0.3", + "compare-versions": "^3.6.0", "concurrent-queue": "7.0.2", "cookie-parser": "1.4.7", "cors": "2.8.5", @@ -94,8 +94,8 @@ "is-elevated": "3.0.0", "jose": "^4.15.9", "js-yaml": "4.1.1", - "jsonschema": "1.4.1", - "keycloak-connect": "^26.1.1", + "jsonschema": "1.5.0", + "openid-client": "^6.8.4", "moment": "2.30.1", "multer": "1.4.5-lts.1", "mysql2": "3.10.1", From dc86cf8d06656aa94515b5810cd999de165db078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 18:11:08 +0300 Subject: [PATCH 35/75] Add generic OIDC configuration and wire bearer validation into the server. Introduce issuer discovery and JWKS verification, map OIDC_* env vars, keep a thin keycloak shim, and derive Viewer auth settings from the issuer URL. --- src/config/config.yaml | 13 +-- src/config/env-mapping.js | 13 +-- src/config/keycloak.js | 121 +++-------------------- src/config/oidc.js | 198 ++++++++++++++++++++++++++++++++++++++ src/server.js | 29 ++++-- src/websocket/server.js | 13 +-- 6 files changed, 242 insertions(+), 145 deletions(-) create mode 100644 src/config/oidc.js diff --git a/src/config/config.yaml b/src/config/config.yaml index b93d990f..33484fd1 100644 --- a/src/config/config.yaml +++ b/src/config/config.yaml @@ -93,16 +93,13 @@ database: min: 0 # Minimum connections idle: 20000 # Idle timeout in milliseconds -# Auth Configuration +# Auth Configuration (OIDC — any compliant provider; env: OIDC_* per naming-map §13) # auth: -# realm: # Keycloak realm -# realmKey: # Realm public key -# url: # Keycloak authentication server URL -# sslRequired: # SSL requirement level +# issuerUrl: # OIDC issuer URL (OIDC_ISSUER_URL); discovery resolves JWKS and endpoints # client: -# id: # ControllerClient ID -# secret: # ControllerClient Client secret -# viewerClient: # Viewer client ID +# id: # Controller API client ID (OIDC_CLIENT_ID) +# secret: # Controller API client secret (OIDC_CLIENT_SECRET) +# viewerClient: # ECN Viewer SPA client ID (OIDC_VIEWER_CLIENT_ID) # Bridge Ports Configuration for Services bridgePorts: diff --git a/src/config/env-mapping.js b/src/config/env-mapping.js index 9890d5f4..ca7cabae 100644 --- a/src/config/env-mapping.js +++ b/src/config/env-mapping.js @@ -74,14 +74,11 @@ module.exports = { path: (provider) => `database.${provider}.sslCA` }, - // Auth Configuration - 'KC_REALM': 'auth.realm', - 'KC_REALM_KEY': 'auth.realmKey', - 'KC_URL': 'auth.url', - 'KC_SSL_REQ': 'auth.sslRequired', - 'KC_CLIENT': 'auth.client.id', - 'KC_CLIENT_SECRET': 'auth.client.secret', - 'KC_VIEWER_CLIENT': 'auth.viewerClient', + // Auth Configuration (OIDC — k8s-style; naming-map §13) + 'OIDC_ISSUER_URL': 'auth.issuerUrl', + 'OIDC_CLIENT_ID': 'auth.client.id', + 'OIDC_CLIENT_SECRET': 'auth.client.secret', + 'OIDC_VIEWER_CLIENT_ID': 'auth.viewerClient', // Bridge Ports Configuration 'BRIDGE_PORTS_RANGE': 'bridgePorts.range', diff --git a/src/config/keycloak.js b/src/config/keycloak.js index 0f510064..164bf8a4 100644 --- a/src/config/keycloak.js +++ b/src/config/keycloak.js @@ -1,119 +1,24 @@ -const session = require('express-session') -const Keycloak = require('keycloak-connect') -const config = require('./index') -const logger = require('../logger') - -// Mock Keycloak implementation for development mode -class MockKeycloak { - constructor () { - this.protect = (roles) => { - return async (req, res, next) => { - // In dev mode, we just add mock user info to the request - req.kauth = { - grant: { - access_token: { - content: { - preferred_username: 'dev-user', - realm_access: { - roles: ['SRE', 'Developer', 'Viewer'] - } - } - } - } - } - return next() - } - } - - // Add middleware method to match real Keycloak interface - this.middleware = () => { - return (req, res, next) => { - // In dev mode, we just pass through the middleware - return next() - } - } - } -} - -const keycloakConfig = { - realm: process.env.KC_REALM || config.get('auth.realm'), - 'realm-public-key': process.env.KC_REALM_KEY || config.get('auth.realmKey'), - 'auth-server-url': process.env.KC_URL || config.get('auth.url'), - 'ssl-required': process.env.KC_SSL_REQ || config.get('auth.sslRequired'), - resource: process.env.KC_CLIENT || config.get('auth.client.id'), - 'bearer-only': true, - 'verify-token-audience': true, - credentials: { - secret: process.env.KC_CLIENT_SECRET || config.get('auth.client.secret') - }, - 'use-resource-role-mappings': true, - 'confidential-port': 0 -} - -let keycloak -let memoryStore - -function isAuthConfigured () { - const requiredConfigs = [ - 'auth.realm', - 'auth.realmKey', - 'auth.url', - 'auth.client.id', - 'auth.client.secret' - ] - return requiredConfigs.every(configKey => { - const value = config.get(configKey) - return value !== undefined && value !== null && value !== '' - }) -} +/** + * Temporary shim for server.js until phase 8-3 migrates to ./oidc.js. + */ +const { + initOidc, + getOidc, + getOidcMiddleware, + getMemoryStore +} = require('./oidc') function initKeycloak () { - if (keycloak) { - return keycloak - } - - const isDevMode = config.get('server.devMode', true) - const hasAuthConfig = isAuthConfigured() - - if (!hasAuthConfig && isDevMode) { - // Initialize mock Keycloak for development - keycloak = new MockKeycloak() - logger.warn('Keycloak initialized in development mode (no auth configuration)') - logger.warn('WARNING: All routes are unprotected in this mode') - } else if (!hasAuthConfig) { - // Throw error in production if auth not configured - const error = new Error('Auth configuration required in production mode') - logger.error('Failed to initialize Keycloak:', error) - throw error - } else { - // Initialize real Keycloak - try { - memoryStore = new session.MemoryStore() - keycloak = new Keycloak({ store: memoryStore }, keycloakConfig) - logger.info('Keycloak initialized successfully with auth configuration') - } catch (error) { - logger.error('Error initializing Keycloak:', error) - throw error - } - } - - return keycloak + return initOidc() } function getKeycloak () { - if (keycloak) { - return keycloak - } -} - -function getMemoryStore () { - if (memoryStore) { - return memoryStore - } + return getOidc() } module.exports = { initKeycloak, + getKeycloak, getMemoryStore, - getKeycloak + getOidcMiddleware } diff --git a/src/config/oidc.js b/src/config/oidc.js new file mode 100644 index 00000000..10fc6234 --- /dev/null +++ b/src/config/oidc.js @@ -0,0 +1,198 @@ +/** + * v3.9+ mTLS user-auth extension points (not implemented in v3.8 — comments only) + * + * Cluster manager API (`/api/v3/*` user routes, operator WebSockets, ECN Viewer login) + * may accept mutual TLS client certificates as an alternative to Bearer JWTs. + * Agent routes (`/api/v3/agent/*`) keep fog-token auth; no OIDC/mTLS on agent wire. + * + * Planned insertion points in this module: + * 1. initOidc() — when HTTPS listener uses requestCert, stash TLS policy flags + * (e.g. require client cert vs optional) for middleware to read via req.socket. + * 2. getOidcMiddleware() — before Bearer branch: if no Authorization header and + * req.socket.authorized, extract identity from getPeerCertificate() (or forwarded + * cert from ingress) and populate req.kauth via buildKauthGrant() for RBAC parity. + * 3. buildKauthGrant() — add cert-derived claims mapper (SAN/CN → preferred_username). + * 4. New export getMtlsMiddleware() (v3.9) — composable with getOidcMiddleware() in + * server.js; Bearer takes precedence when both are present. + * 5. ensureDiscovery() — optional cert-bound token exchange at issuer (RFC 8705) if + * provider requires OIDC token alongside mTLS termination. + * + * Edgelet CP: no v3.8 env changes for mTLS; Plan 8.1 embedded issuer is separate. + * See .cursor/rules/controller-oidc-handoff.mdc for v3.8 OIDC env contract. + */ +const session = require('express-session') +const oidcClient = require('openid-client') +const { createRemoteJWKSet, jwtVerify } = require('jose') +const config = require('./index') +const logger = require('../logger') + +let oidcInstance = null +let memoryStore = null +let discoveryPromise = null +let jwks = null +let issuerString = null +let configuredClientId = null +let devMode = false + +function getOidcSettings () { + return { + issuerUrl: process.env.OIDC_ISSUER_URL || config.get('auth.issuerUrl') || config.get('auth.url'), + clientId: process.env.OIDC_CLIENT_ID || config.get('auth.client.id'), + clientSecret: process.env.OIDC_CLIENT_SECRET || config.get('auth.client.secret') + } +} + +function isAuthConfigured () { + const { issuerUrl, clientId, clientSecret } = getOidcSettings() + return [issuerUrl, clientId, clientSecret].every( + value => value !== undefined && value !== null && value !== '' + ) +} + +/** + * req.kauth compatibility shape for route handlers; populated by config/oidc.js bearer middleware. + */ +function buildKauthGrant (claims, rawToken) { + return { + grant: { + access_token: { + token: rawToken, + content: claims + } + } + } +} + +async function ensureDiscovery () { + if (discoveryPromise) { + return discoveryPromise + } + + const { issuerUrl, clientId, clientSecret } = getOidcSettings() + configuredClientId = clientId + + discoveryPromise = (async () => { + const issuer = new URL(issuerUrl) + const configuration = await oidcClient.discovery( + issuer, + clientId, + clientSecret + ) + const metadata = configuration.serverMetadata() + + if (!metadata.jwks_uri) { + throw new Error('OIDC issuer discovery did not return jwks_uri') + } + + jwks = createRemoteJWKSet(new URL(metadata.jwks_uri)) + issuerString = metadata.issuer + return configuration + })().catch((error) => { + discoveryPromise = null + throw error + }) + + return discoveryPromise +} + +function createOidcFacade () { + return { + middleware () { + return getOidcMiddleware() + } + } +} + +function initOidc () { + if (oidcInstance) { + return oidcInstance + } + + // v3.9: read TLS client-auth policy when HTTPS + requestCert enabled (server.js listener) + const isDevMode = config.get('server.devMode', true) + const hasAuthConfig = isAuthConfigured() + devMode = isDevMode && !hasAuthConfig + + if (devMode) { + oidcInstance = createOidcFacade() + logger.warn('OIDC initialized in development mode (no auth configuration)') + logger.warn('WARNING: All routes are unprotected in this mode') + return oidcInstance + } + + if (!hasAuthConfig) { + const error = new Error('Auth configuration required in production mode') + logger.error('Failed to initialize OIDC:', error) + throw error + } + + memoryStore = new session.MemoryStore() + oidcInstance = createOidcFacade() + logger.info('OIDC initialized successfully with auth configuration') + return oidcInstance +} + +function getOidc () { + return oidcInstance || initOidc() +} + +function getOidcMiddleware () { + return async (req, res, next) => { + if (devMode) { + return next() + } + + // Agent routes use fog JWTs (checkFogToken), not OIDC bearer tokens + const requestPath = req.path || (req.url && req.url.split('?')[0]) || '' + if (requestPath.startsWith('/api/v3/agent')) { + return next() + } + + // v3.9: if no Bearer token and req.socket.authorized, map client cert → req.kauth here + const authHeader = req.headers.authorization + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return next() + } + + const token = authHeader.slice('Bearer '.length).trim() + if (!token) { + return next() + } + + try { + await ensureDiscovery() + const verifyOptions = { issuer: issuerString } + if (configuredClientId) { + verifyOptions.audience = configuredClientId + } + + const { payload } = await jwtVerify(token, jwks, verifyOptions) + req.kauth = buildKauthGrant(payload, token) + return next() + } catch (error) { + logger.warn({ + msg: 'OIDC bearer token validation failed', + err: error.message || String(error) + }) + return next() + } + } +} + +function getMemoryStore () { + return memoryStore +} + +async function getOidcConfiguration () { + return ensureDiscovery() +} + +module.exports = { + initOidc, + getOidc, + getOidcMiddleware, + getMemoryStore, + isAuthConfigured, + getOidcSettings, + getOidcConfiguration +} diff --git a/src/server.js b/src/server.js index 47650a0a..23efd4fd 100755 --- a/src/server.js +++ b/src/server.js @@ -37,11 +37,11 @@ initialize().then(() => { storage: multerMemStorage }).single(fileName) - // Initialize session and Keycloak after config is loaded + // Initialize session and OIDC bearer validation after config is loaded const session = require('express-session') - const { initKeycloak, getMemoryStore } = require('./config/keycloak.js') + const { initOidc, getOidcMiddleware, getMemoryStore, getOidcSettings } = require('./config/oidc.js') const memoryStore = getMemoryStore() - const keycloak = initKeycloak() + initOidc() const viewerApp = express() const app = express() @@ -59,7 +59,7 @@ initialize().then(() => { saveUninitialized: true, store: memoryStore })) - app.use(keycloak.middleware()) + app.use(getOidcMiddleware()) app.use(bodyParser.urlencoded({ extended: true })) @@ -222,9 +222,18 @@ initialize().then(() => { const hasFileBasedSSL = !devMode && sslKey && sslCert const hasBase64SSL = !devMode && sslKeyBase64 && sslCertBase64 - const kcRealm = process.env.KC_REALM || config.get('auth.realm') - const kcURL = process.env.KC_URL || config.get('auth.url') - const kcClient = process.env.KC_VIEWER_CLIENT || config.get('auth.viewerClient') + const { issuerUrl: oidcIssuerUrl } = getOidcSettings() + const oidcViewerClient = process.env.OIDC_VIEWER_CLIENT_ID || config.get('auth.viewerClient') + // ECN Viewer still reads keycloak* keys in controller-config.js; derive from OIDC issuer when possible + let viewerAuthUrl = oidcIssuerUrl || '' + let viewerAuthRealm = '' + if (oidcIssuerUrl) { + const realmMatch = oidcIssuerUrl.match(/^(.*)\/realms\/([^/]+)\/?$/) + if (realmMatch) { + viewerAuthUrl = `${realmMatch[1]}/` + viewerAuthRealm = realmMatch[2] + } + } viewerApp.use('/', ecnViewer.middleware(express)) @@ -255,9 +264,9 @@ initialize().then(() => { port: apiPort, user: {}, controllerDevMode: devMode, - keycloakUrl: kcURL, - keycloakRealm: kcRealm, - keycloakClientId: kcClient + keycloakUrl: viewerAuthUrl, + keycloakRealm: viewerAuthRealm, + keycloakClientId: oidcViewerClient } if (viewerURL) { ecnViewerControllerConfig.url = viewerURL diff --git a/src/websocket/server.js b/src/websocket/server.js index 8ede8b03..878ef95a 100644 --- a/src/websocket/server.js +++ b/src/websocket/server.js @@ -35,6 +35,7 @@ const MESSAGE_TYPES = { } const EventService = require('../services/event-service') +const { isAuthConfigured: isOidcAuthConfigured } = require('../config/oidc') let processErrorHandlersRegistered = false @@ -2331,17 +2332,7 @@ class WebSocketServer { // Helper method to check if auth is configured isAuthConfigured () { - const requiredConfigs = [ - 'auth.realm', - 'auth.realmKey', - 'auth.url', - 'auth.client.id', - 'auth.client.secret' - ] - return requiredConfigs.every(configKey => { - const value = config.get(configKey) - return value !== undefined && value !== null && value !== '' - }) + return isOidcAuthConfigured() } // Helper method to validate ISO 8601 format From 7f299ae6558c9ca092816617a50f37700001e91f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 18:11:12 +0300 Subject: [PATCH 36/75] Extract RBAC subjects from generic OIDC access token claims. Read client id from OIDC settings and map preferred_username, roles, groups, and resource_access into protect() subjects. --- src/lib/rbac/authorizer.js | 2 +- src/lib/rbac/middleware.js | 160 ++++++++++++++++--------------------- 2 files changed, 72 insertions(+), 90 deletions(-) diff --git a/src/lib/rbac/authorizer.js b/src/lib/rbac/authorizer.js index 87faaedb..520be971 100644 --- a/src/lib/rbac/authorizer.js +++ b/src/lib/rbac/authorizer.js @@ -141,7 +141,7 @@ async function authorize (subjects, apiGroup, resource, verb, resourceName, tran } // Check system roles first (Admin, SRE, Developer, Viewer) - // These work directly without RoleBindings when Keycloak role name matches + // These work directly without RoleBindings when OIDC group/role name matches for (const subject of subjects) { if (subject.kind === 'Group' && subject.name) { const roleName = subject.name.toLowerCase() diff --git a/src/lib/rbac/middleware.js b/src/lib/rbac/middleware.js index 017b176c..1c6abbb1 100644 --- a/src/lib/rbac/middleware.js +++ b/src/lib/rbac/middleware.js @@ -17,6 +17,7 @@ const yaml = require('js-yaml') const authorizer = require('./authorizer') const logger = require('../../logger') const config = require('../../config') +const { getOidcSettings } = require('../../config/oidc') // Load route resource catalog let routeCatalog = null @@ -36,55 +37,75 @@ function loadRouteCatalog () { } } +function getAuthClientId () { + const { clientId } = getOidcSettings() + return clientId || config.get('auth.client.id') +} + +function extractUserSubject (tokenContent) { + const username = tokenContent.preferred_username + || tokenContent.username + || tokenContent.email + || tokenContent.sub + if (!username) { + return null + } + return { + kind: 'User', + name: String(username) + } +} + +function extractGroupSubjects (tokenContent) { + const groups = [] + const clientId = getAuthClientId() + + if (clientId && tokenContent.resource_access && tokenContent.resource_access[clientId]) { + const clientRoles = tokenContent.resource_access[clientId].roles || [] + for (const role of clientRoles) { + groups.push({ + kind: 'Group', + name: role.toLowerCase() + }) + } + } + + if (Array.isArray(tokenContent.roles)) { + for (const role of tokenContent.roles) { + groups.push({ + kind: 'Group', + name: String(role).toLowerCase() + }) + } + } + + if (Array.isArray(tokenContent.groups)) { + for (const group of tokenContent.groups) { + groups.push({ + kind: 'Group', + name: String(group).toLowerCase() + }) + } + } + + return groups +} + /** - * Extract subjects from request (Keycloak token or ServiceAccount JWT) + * Extract subjects from request (OIDC bearer token via req.kauth or ServiceAccount JWT) */ function extractSubjects (req) { const subjects = [] - // Extract from Keycloak token if (req.kauth && req.kauth.grant && req.kauth.grant.access_token) { const tokenContent = req.kauth.grant.access_token.content - // Extract user - if (tokenContent.preferred_username) { - subjects.push({ - kind: 'User', - name: tokenContent.preferred_username - }) - } - - // // Extract groups from realm_access.roles or groups claim - // if (tokenContent.realm_access && tokenContent.realm_access.roles) { - // for (const role of tokenContent.realm_access.roles) { - // subjects.push({ - // kind: 'Group', - // name: role.toLowerCase() - // }) - // } - // } - - // Extract roles from resource_access[clientId].roles (client-specific roles) - const clientId = process.env.KC_CLIENT || config.get('auth.client.id') - if (clientId && tokenContent.resource_access && tokenContent.resource_access[clientId]) { - const clientRoles = tokenContent.resource_access[clientId].roles || [] - for (const role of clientRoles) { - subjects.push({ - kind: 'Group', - name: role.toLowerCase() - }) - } + const user = extractUserSubject(tokenContent) + if (user) { + subjects.push(user) } - // // Extract groups from groups claim (if available) - // if (tokenContent.groups && Array.isArray(tokenContent.groups)) { - // for (const group of tokenContent.groups) { - // subjects.push({ - // kind: 'Group', - // name: group - // }) - // } - // } + subjects.push(...extractGroupSubjects(tokenContent)) } // // Extract from ServiceAccount JWT (if present in Authorization header) @@ -182,57 +203,18 @@ function findRouteDefinition (method, path) { function extractSubjectsFromWebSocket (req, token) { const subjects = [] - // For WebSocket, we need to verify the token and extract subjects - // This will be called from WebSocket handlers where token is already extracted if (token) { try { - // Parse JWT token to extract user info const tokenParts = token.replace('Bearer ', '').split('.') if (tokenParts.length === 3) { const payload = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString()) - - // Extract user - if (payload.preferred_username || payload.sub) { - subjects.push({ - kind: 'User', - name: payload.preferred_username || payload.sub - }) - } - // // Extract groups from realm_access.roles - // // Commented out for security - only use client-scope roles - // if (payload.realm_access && payload.realm_access.roles) { - // for (const role of payload.realm_access.roles) { - // subjects.push({ - // kind: 'Group', - // name: role.toLowerCase() - // }) - // } - // } - - // Extract roles from resource_access[clientId].roles (client-specific roles) - // This is the secure approach - only client-scope roles - const clientId = process.env.KC_CLIENT || config.get('auth.client.id') - if (clientId && payload.resource_access && payload.resource_access[clientId]) { - const clientRoles = payload.resource_access[clientId].roles || [] - for (const role of clientRoles) { - subjects.push({ - kind: 'Group', - name: role.toLowerCase() - }) - } + const user = extractUserSubject(payload) + if (user) { + subjects.push(user) } - // // Extract groups from groups claim - // // Commented out - not used for RBAC authorization - // if (payload.groups && Array.isArray(payload.groups)) { - // for (const group of payload.groups) { - // subjects.push({ - // kind: 'Group', - // name: group - // }) - // } - // } + subjects.push(...extractGroupSubjects(payload)) } } catch (error) { logger.warn('Failed to extract subjects from WebSocket token:', error) @@ -472,20 +454,20 @@ function protectWebSocket (handler) { } /** - * RBAC protect function - drop-in replacement for keycloak.protect() - * + * RBAC protect function — OIDC-aware authorization gate (keycloak.protect() replacement) + * * NOTE: The roles parameter is IGNORED. Authorization is determined automatically * from the route catalog (rbac-resources.yaml) and RoleBindings in the database. - * + * * Do NOT pass static role arrays. All authorization is based on fine-grained RBAC rules * defined via Role and RoleBinding objects. - * + * * @param {string|Array} _roles - IGNORED (kept for backward compatibility only) - * @returns {Function} Middleware function compatible with keycloak.protect() pattern + * @returns {Function} Middleware function compatible with legacy keycloak.protect() signature */ function protect (_roles) { // _roles parameter is ignored - authorization determined from route catalog and RoleBindings - // Return a function that matches keycloak.protect() signature: (req, res, callback) => {} + // Return a function that matches legacy protect() signature: (req, res, callback) => {} return async (req, res, callback) => { try { // Extract subjects from request @@ -549,7 +531,7 @@ function protect (_roles) { module.exports = { requirePermission, - protect, // Drop-in replacement for keycloak.protect() + protect, // OIDC-aware replacement for legacy keycloak.protect() authorizeWebSocket, protectWebSocket, // RBAC protection for WebSocket connections skipRBAC, From f76d7b62a1da2cc8d0790f54b4e5e180cd2cc083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 18:11:17 +0300 Subject: [PATCH 37/75] Migrate user login, refresh, profile, and logout to openid-client. Use discovery-backed token and userinfo endpoints instead of hardcoded Keycloak URLs and KC_* environment variables. --- src/services/user-service.js | 219 ++++++++++++----------------------- 1 file changed, 71 insertions(+), 148 deletions(-) diff --git a/src/services/user-service.js b/src/services/user-service.js index 639b1fef..4e9e3444 100644 --- a/src/services/user-service.js +++ b/src/services/user-service.js @@ -13,15 +13,16 @@ const Errors = require('../helpers/errors') const TransactionDecorator = require('../decorators/transaction-decorator') -const axios = require('axios') -const qs = require('qs') -const https = require('https') const config = require('../config') +const { + genericGrantRequest, + refreshTokenGrant, + fetchUserInfo, + tokenRevocation +} = require('openid-client') +const { decodeJwt } = require('jose') +const { getOidcConfiguration, isAuthConfigured } = require('../config/oidc') -const kcClient = process.env.KC_CLIENT || config.get('auth.client.id') -const kcClientSecret = process.env.KC_CLIENT_SECRET || config.get('auth.client.secret') -const kcUrl = process.env.KC_URL || config.get('auth.url') -const kcRealm = process.env.KC_REALM || config.get('auth.realm') const isDevMode = config.get('server.devMode', true) const mockUser = { @@ -37,196 +38,118 @@ const mockToken = { refresh_token: 'mock-refresh-token' } -const isAuthConfigured = () => { - return kcUrl && kcRealm && kcClient && kcClientSecret +function mapOidcError (error) { + const description = error.error_description || error.message || 'Invalid credentials' + throw new Errors.InvalidCredentialsError(description) } -const login = async function (credentials, isCLI, transaction) { - // If in dev mode and auth is not configured, always return mock token +function tokensFromResponse (tokenResponse) { + return { + accessToken: tokenResponse.access_token, + refreshToken: tokenResponse.refresh_token + } +} + +function ensureAuthOrDev () { if (!isAuthConfigured() && isDevMode) { - return { - accessToken: mockToken.access_token, - refreshToken: mockToken.refresh_token - } + return 'dev' } - // If auth is not configured and not in dev mode, throw error if (!isAuthConfigured() && !isDevMode) { - throw new Error(`Auth is not configured for this cluster. Please contact your administrator.`) + throw new Error('Auth is not configured for this cluster. Please contact your administrator.') } - // Only proceed with axios request if auth is configured - const data = qs.stringify({ - grant_type: 'password', - username: credentials.email, - password: credentials.password, - totp: credentials.totp, - client_id: kcClient, - client_secret: kcClientSecret - }) - - const agent = new https.Agent({ - rejectUnauthorized: false - }) - - const requestConfig = { - method: 'post', - maxBodyLength: Infinity, - url: `${kcUrl}realms/${kcRealm}/protocol/openid-connect/token`, - headers: { - 'Cache-Control': 'no-cache', - 'Content-Type': 'application/x-www-form-urlencoded' - }, - data, - httpsAgent: agent + return 'oidc' +} + +const login = async function (credentials, isCLI, transaction) { + const mode = ensureAuthOrDev() + if (mode === 'dev') { + return { + accessToken: mockToken.access_token, + refreshToken: mockToken.refresh_token + } } try { - const response = await axios.request(requestConfig) - const accessToken = response.data.access_token - const refreshToken = response.data.refresh_token - return { - accessToken, - refreshToken + const oidcConfig = await getOidcConfiguration() + const parameters = { + username: credentials.email, + password: credentials.password } - } catch (error) { - if (error.response && error.response.data) { - throw new Errors.InvalidCredentialsError(error.response.data.error_description || 'Invalid credentials') + if (credentials.totp) { + parameters.totp = credentials.totp } - throw new Errors.InvalidCredentialsError(error.message || 'Invalid credentials') + + const tokenResponse = await genericGrantRequest(oidcConfig, 'password', parameters) + return tokensFromResponse(tokenResponse) + } catch (error) { + mapOidcError(error) } } const refresh = async function (credentials, isCLI, transaction) { - // If in dev mode and auth is not configured, always return mock token - if (!isAuthConfigured() && isDevMode) { + const mode = ensureAuthOrDev() + if (mode === 'dev') { return { accessToken: mockToken.access_token, refreshToken: mockToken.refresh_token } } - // If auth is not configured and not in dev mode, throw error - if (!isAuthConfigured() && !isDevMode) { - throw new Error(`Auth is not configured for this cluster. Please contact your administrator.`) - } - - // Only proceed with axios request if auth is configured - const data = qs.stringify({ - grant_type: 'refresh_token', - refresh_token: credentials.refreshToken, - client_id: kcClient, - client_secret: kcClientSecret - }) - - const agent = new https.Agent({ - rejectUnauthorized: false - }) - - const requestConfig = { - method: 'post', - maxBodyLength: Infinity, - url: `${kcUrl}realms/${kcRealm}/protocol/openid-connect/token`, - headers: { - 'Cache-Control': 'no-cache', - 'Content-Type': 'application/x-www-form-urlencoded' - }, - data, - httpsAgent: agent - } - try { - const response = await axios.request(requestConfig) - const accessToken = response.data.access_token - const refreshToken = response.data.refresh_token - return { - accessToken, - refreshToken - } + const oidcConfig = await getOidcConfiguration() + const tokenResponse = await refreshTokenGrant(oidcConfig, credentials.refreshToken) + return tokensFromResponse(tokenResponse) } catch (error) { - if (error.response && error.response.data) { - throw new Errors.InvalidCredentialsError(error.response.data.error_description || 'Invalid credentials') - } - throw new Errors.InvalidCredentialsError(error.message || 'Invalid credentials') + mapOidcError(error) } } const profile = async function (req, isCLI, transaction) { - // If in dev mode and auth is not configured, always return mock user - if (!isAuthConfigured() && isDevMode) { + const mode = ensureAuthOrDev() + if (mode === 'dev') { return mockUser } - // If auth is not configured and not in dev mode, throw error - if (!isAuthConfigured() && !isDevMode) { - throw new Error(`Auth is not configured for this cluster. Please contact your administrator.`) - } - - // Only proceed with axios request if auth is configured const accessToken = req.headers.authorization.replace('Bearer ', '') - const agent = new https.Agent({ - rejectUnauthorized: false - }) - - const requestConfig = { - method: 'get', - maxBodyLength: Infinity, - url: `${kcUrl}realms/${kcRealm}/protocol/openid-connect/userinfo`, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - Authorization: `Bearer ${accessToken}` - }, - httpsAgent: agent - } try { - const response = await axios.request(requestConfig) - return response.data + const oidcConfig = await getOidcConfiguration() + const claims = decodeJwt(accessToken) + const subject = claims.sub + if (!subject) { + throw new Errors.InvalidCredentialsError('Invalid credentials') + } + + return await fetchUserInfo(oidcConfig, accessToken, subject) } catch (error) { - if (error.response && error.response.data) { - throw new Errors.InvalidCredentialsError(error.response.data.error_description || 'Invalid credentials') + if (error instanceof Errors.InvalidCredentialsError) { + throw error } - throw new Errors.InvalidCredentialsError(error.message || 'Invalid credentials') + mapOidcError(error) } } const logout = async function (req, isCLI, transaction) { - // If in dev mode and auth is not configured, always return success - if (!isAuthConfigured() && isDevMode) { + const mode = ensureAuthOrDev() + if (mode === 'dev') { return { status: 'success' } } - // If auth is not configured and not in dev mode, throw error - if (!isAuthConfigured() && !isDevMode) { - throw new Error(`Auth is not configured for this cluster. Please contact your administrator.`) - } - - // Only proceed with axios request if auth is configured const accessToken = req.headers.authorization.replace('Bearer ', '') - const agent = new https.Agent({ - rejectUnauthorized: false - }) - - const requestConfig = { - method: 'post', - maxBodyLength: Infinity, - url: `${kcUrl}realms/${kcRealm}/protocol/openid-connect/logout`, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - Authorization: `Bearer ${accessToken}` - }, - httpsAgent: agent - } try { - const response = await axios.request(requestConfig) - return response.data - } catch (error) { - if (error.response && error.response.data) { - throw new Errors.InvalidCredentialsError(error.response.data.error_description || 'Invalid credentials') + const oidcConfig = await getOidcConfiguration() + const metadata = oidcConfig.serverMetadata() + if (metadata.revocation_endpoint) { + await tokenRevocation(oidcConfig, accessToken, { token_type_hint: 'access_token' }) } - throw new Errors.InvalidCredentialsError(error.message || 'Invalid credentials') + } catch (error) { + // Best-effort logout when issuer has no revocation endpoint or revocation fails } + + return { status: 'success' } } module.exports = { From 6b91485fa7db4278e1caee319e4991d5b703e20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 18:11:35 +0300 Subject: [PATCH 38/75] Add mock OIDC issuer and tests for bearer middleware and user auth flows. Cover discovery, JWKS validation, RBAC subject extraction, and login/refresh/profile/logout against a local HTTPS mock provider. --- test/oidc/README.md | 67 +++++ test/oidc/run-mock-provider.js | 49 ++++ test/src/config/oidc.test.js | 146 +++++++++++ test/src/lib/rbac/middleware-oidc.test.js | 188 ++++++++++++++ test/src/services/user-service-oidc.test.js | 145 +++++++++++ test/src/support/mock-oidc-smoke.test.js | 35 +++ test/support/mock-oidc-provider.js | 262 ++++++++++++++++++++ test/support/oidc-smoke.js | 40 +++ test/support/oidc-test-helpers.js | 92 +++++++ 9 files changed, 1024 insertions(+) create mode 100644 test/oidc/README.md create mode 100644 test/oidc/run-mock-provider.js create mode 100644 test/src/config/oidc.test.js create mode 100644 test/src/lib/rbac/middleware-oidc.test.js create mode 100644 test/src/services/user-service-oidc.test.js create mode 100644 test/src/support/mock-oidc-smoke.test.js create mode 100644 test/support/mock-oidc-provider.js create mode 100644 test/support/oidc-smoke.js create mode 100644 test/support/oidc-test-helpers.js diff --git a/test/oidc/README.md b/test/oidc/README.md new file mode 100644 index 00000000..c8c269d3 --- /dev/null +++ b/test/oidc/README.md @@ -0,0 +1,67 @@ +# OIDC mock provider (Plan 8) + +Generic OIDC smoke path for dev and CI. Replaces the legacy `MockKeycloak` dev shim with a +provider-agnostic mock that exercises the same discovery + JWKS + bearer validation flow as +production. + +## Unit tests + +OIDC and RBAC middleware tests live under: + +- `test/src/config/oidc.test.js` +- `test/src/lib/rbac/middleware-oidc.test.js` +- `test/src/support/mock-oidc-smoke.test.js` + +Run only OIDC-related tests: + +```bash +nvm use 24 +node ./node_modules/mocha/bin/mocha.js test/src/config/oidc.test.js test/src/lib/rbac/middleware-oidc.test.js test/src/support/mock-oidc-smoke.test.js --require test/support/setup.js --ui bdd-lazy-var/global --grep 'OIDC|Mock OIDC' --exit +``` + +## Local dev smoke + +1. Start the mock issuer: + + ```bash + node test/oidc/run-mock-provider.js + ``` + +2. Export the printed `OIDC_*` variables in the shell where you run Controller. + +3. Ensure `server.devMode` is true (default in `config.yaml`) or set auth in yaml — the mock + env vars take precedence over an empty auth block. + +4. For the self-signed mock cert, also run: + + ```bash + export NODE_TLS_REJECT_UNAUTHORIZED=0 + ``` + +5. Start Controller (`npm run start-dev`) and call a RBAC-protected route with: + + ```bash + curl -H "Authorization: Bearer " http://localhost:51121/api/v3/... + ``` + + Issue a token from Node (example): + + ```javascript + const { MockOidcProvider } = require('./test/support/mock-oidc-provider') + const p = new MockOidcProvider({ clientId: '...', clientSecret: '...' }) + await p.start() + const token = await p.issueAccessToken({ preferred_username: 'smoke-user', roles: ['sre'] }) + ``` + +6. Without a bearer token, protected routes should return `401`. With a valid token, RBAC + applies from `rbac-resources.yaml` and RoleBindings as before. + +## Real provider smoke + +Point the same env vars at any OIDC issuer (Keycloak, Auth0, etc.): + +- `OIDC_ISSUER_URL` — issuer URL (discovery document at `/.well-known/openid-configuration`) +- `OIDC_CLIENT_ID` — confidential client for Controller API +- `OIDC_CLIENT_SECRET` — client secret + +No Keycloak-specific env vars are required. diff --git a/test/oidc/run-mock-provider.js b/test/oidc/run-mock-provider.js new file mode 100644 index 00000000..b5cff8ad --- /dev/null +++ b/test/oidc/run-mock-provider.js @@ -0,0 +1,49 @@ +#!/usr/bin/env node +/* + * Standalone mock OIDC issuer for local dev smoke runs. + * + * Usage: + * node test/oidc/run-mock-provider.js + * + * Then point Controller at the printed OIDC_* values and restart start-dev. + */ + +const { MockOidcProvider } = require('../support/mock-oidc-provider') +const { enableMockOidcTls } = require('../support/oidc-test-helpers') + +async function main () { + enableMockOidcTls() + const provider = new MockOidcProvider() + await provider.start() + + const env = provider.getEnv() + console.log('Mock OIDC provider listening') + console.log(` issuer: ${env.OIDC_ISSUER_URL}`) + console.log('') + console.log('Export for Controller:') + for (const [key, value] of Object.entries(env)) { + console.log(` export ${key}='${value}'`) + } + console.log('') + console.log('For local smoke, also run:') + console.log(" export NODE_TLS_REJECT_UNAUTHORIZED=0") + console.log('') + console.log('Press Ctrl+C to stop.') + + const shutdown = async () => { + await provider.stop() + process.exit(0) + } + + process.on('SIGINT', shutdown) + process.on('SIGTERM', shutdown) +} + +if (require.main === module) { + main().catch((error) => { + console.error(error) + process.exit(1) + }) +} + +module.exports = { main } diff --git a/test/src/config/oidc.test.js b/test/src/config/oidc.test.js new file mode 100644 index 00000000..25352bf3 --- /dev/null +++ b/test/src/config/oidc.test.js @@ -0,0 +1,146 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const config = require('../../../src/config') +const { MockOidcProvider } = require('../../support/mock-oidc-provider') +const { + snapshotOidcEnv, + restoreOidcEnv, + applyOidcEnv, + enableMockOidcTls, + restoreMockOidcTls, + reloadOidcModule, + runMiddleware +} = require('../../support/oidc-test-helpers') + +describe('OIDC config', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('provider', () => new MockOidcProvider()) + + beforeEach(async () => { + enableMockOidcTls() + await $provider.start() + }) + + afterEach(async () => { + $sandbox.restore() + restoreOidcEnv($envSnapshot) + restoreMockOidcTls() + await $provider.stop() + }) + + describe('isAuthConfigured()', () => { + it('returns false when OIDC env vars are unset', () => { + applyOidcEnv({}) + const oidc = reloadOidcModule() + expect(oidc.isAuthConfigured()).to.equal(false) + }) + + it('returns true when issuer, client id, and secret are set', () => { + applyOidcEnv($provider.getEnv()) + const oidc = reloadOidcModule() + expect(oidc.isAuthConfigured()).to.equal(true) + }) + }) + + describe('initOidc() dev mode', () => { + it('uses pass-through middleware when auth is not configured', async () => { + applyOidcEnv({}) + const oidc = reloadOidcModule() + oidc.initOidc() + + const result = await runMiddleware(oidc.getOidcMiddleware(), { + headers: {} + }) + + expect(result.nextCalled).to.equal(true) + expect(result.req.kauth).to.equal(undefined) + }) + }) + + describe('initOidc() production mode', () => { + beforeEach(() => { + const originalGet = config.get.bind(config) + $sandbox.stub(config, 'get').callsFake((key, defaultValue) => { + if (key === 'server.devMode') { + return false + } + return originalGet(key, defaultValue) + }) + }) + + it('throws when auth is not configured', () => { + applyOidcEnv({}) + const oidc = reloadOidcModule() + expect(() => oidc.initOidc()).to.throw('Auth configuration required in production mode') + }) + }) + + describe('getOidcMiddleware() with mock issuer', () => { + beforeEach(() => { + applyOidcEnv($provider.getEnv()) + }) + + it('populates req.kauth for a valid bearer token', async () => { + const oidc = reloadOidcModule() + oidc.initOidc() + const token = await $provider.issueAccessToken({ + preferred_username: 'alice', + roles: ['SRE'] + }) + + const result = await runMiddleware(oidc.getOidcMiddleware(), { + headers: { + authorization: `Bearer ${token}` + } + }) + + expect(result.nextCalled).to.equal(true) + expect(result.req.kauth.grant.access_token.token).to.equal(token) + expect(result.req.kauth.grant.access_token.content.preferred_username).to.equal('alice') + expect(result.req.kauth.grant.access_token.content.roles).to.deep.equal(['SRE']) + }) + + it('leaves req.kauth unset for an invalid bearer token', async () => { + const oidc = reloadOidcModule() + oidc.initOidc() + + const result = await runMiddleware(oidc.getOidcMiddleware(), { + headers: { + authorization: 'Bearer not-a-valid-jwt' + } + }) + + expect(result.nextCalled).to.equal(true) + expect(result.req.kauth).to.equal(undefined) + }) + + it('passes through when Authorization header is missing', async () => { + const oidc = reloadOidcModule() + oidc.initOidc() + + const result = await runMiddleware(oidc.getOidcMiddleware(), { + headers: {} + }) + + expect(result.nextCalled).to.equal(true) + expect(result.req.kauth).to.equal(undefined) + }) + + it('skips OIDC validation for agent routes', async () => { + const oidc = reloadOidcModule() + oidc.initOidc() + + const result = await runMiddleware(oidc.getOidcMiddleware(), { + path: '/api/v3/agent/status', + headers: { + authorization: 'Bearer not-an-oidc-token' + } + }) + + expect(result.nextCalled).to.equal(true) + expect(result.req.kauth).to.equal(undefined) + }) + }) +}) diff --git a/test/src/lib/rbac/middleware-oidc.test.js b/test/src/lib/rbac/middleware-oidc.test.js new file mode 100644 index 00000000..2bbd151b --- /dev/null +++ b/test/src/lib/rbac/middleware-oidc.test.js @@ -0,0 +1,188 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const authorizer = require('../../../../src/lib/rbac/authorizer') +const rbacMiddleware = require('../../../../src/lib/rbac/middleware') +const { MockOidcProvider } = require('../../../support/mock-oidc-provider') +const { + snapshotOidcEnv, + restoreOidcEnv, + applyOidcEnv, + enableMockOidcTls, + restoreMockOidcTls, + reloadOidcModule, + runMiddleware +} = require('../../../support/oidc-test-helpers') + +describe('RBAC middleware OIDC integration', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('provider', () => new MockOidcProvider()) + + beforeEach(async () => { + enableMockOidcTls() + await $provider.start() + }) + + afterEach(async () => { + $sandbox.restore() + restoreOidcEnv($envSnapshot) + restoreMockOidcTls() + await $provider.stop() + }) + + describe('extractSubjects()', () => { + it('extracts user and roles claim subjects', () => { + const req = { + kauth: { + grant: { + access_token: { + content: { + preferred_username: 'alice', + roles: ['SRE', 'Developer'] + } + } + } + } + } + + const subjects = rbacMiddleware.extractSubjects(req) + expect(subjects).to.deep.include({ kind: 'User', name: 'alice' }) + expect(subjects).to.deep.include({ kind: 'Group', name: 'sre' }) + expect(subjects).to.deep.include({ kind: 'Group', name: 'developer' }) + }) + + it('extracts Keycloak-style resource_access roles for the configured client', () => { + applyOidcEnv($provider.getEnv()) + reloadOidcModule() + + const req = { + kauth: { + grant: { + access_token: { + content: { + preferred_username: 'bob', + resource_access: { + [$provider.clientId]: { + roles: ['Viewer'] + } + } + } + } + } + } + } + + const subjects = rbacMiddleware.extractSubjects(req) + expect(subjects).to.deep.include({ kind: 'User', name: 'bob' }) + expect(subjects).to.deep.include({ kind: 'Group', name: 'viewer' }) + }) + + it('returns an empty list when req.kauth is missing', () => { + expect(rbacMiddleware.extractSubjects({})).to.deep.equal([]) + }) + }) + + describe('protect()', () => { + def('callback', () => sinon.spy()) + def('res', () => ({ + statusCode: null, + body: null, + status (code) { + this.statusCode = code + return this + }, + json (payload) { + this.body = payload + return this + } + })) + + it('returns 401 when no authentication information is present', async () => { + const req = { method: 'GET', path: '/api/v3/applications' } + await rbacMiddleware.protect()(req, $res, $callback) + + expect($res.statusCode).to.equal(401) + expect($res.body.error).to.match(/Unauthorized/) + expect($callback).to.not.have.been.called + }) + + it('allows routes that are not listed in the RBAC catalog', async () => { + const req = { + method: 'GET', + path: '/api/v3/not-in-catalog', + kauth: { + grant: { + access_token: { + content: { + preferred_username: 'alice' + } + } + } + } + } + + await rbacMiddleware.protect()(req, $res, $callback) + expect($callback).to.have.been.calledOnce + expect($res.statusCode).to.equal(null) + }) + + it('authorizes catalog routes using subjects from OIDC bearer tokens', async () => { + applyOidcEnv($provider.getEnv()) + const oidc = reloadOidcModule() + oidc.initOidc() + + const token = await $provider.issueAccessToken({ + preferred_username: 'alice', + roles: ['SRE'] + }) + + const middlewareResult = await runMiddleware(oidc.getOidcMiddleware(), { + headers: { + authorization: `Bearer ${token}` + } + }) + + $sandbox.stub(authorizer, 'authorize').resolves({ allowed: true }) + + const req = { + method: 'GET', + path: '/api/v3/microservices/', + kauth: middlewareResult.req.kauth + } + + await rbacMiddleware.protect()(req, $res, $callback) + + expect(authorizer.authorize).to.have.been.calledOnce + expect($callback).to.have.been.calledOnce + expect($res.statusCode).to.equal(null) + }) + + it('returns 403 when authorizer denies access', async () => { + $sandbox.stub(authorizer, 'authorize').resolves({ + allowed: false, + reason: 'insufficient permissions' + }) + + const req = { + method: 'GET', + path: '/api/v3/microservices/', + kauth: { + grant: { + access_token: { + content: { + preferred_username: 'alice' + } + } + } + } + } + + await rbacMiddleware.protect()(req, $res, $callback) + + expect($res.statusCode).to.equal(403) + expect($res.body.error).to.equal('Forbidden') + expect($callback).to.not.have.been.called + }) + }) +}) diff --git a/test/src/services/user-service-oidc.test.js b/test/src/services/user-service-oidc.test.js new file mode 100644 index 00000000..0e3f5b23 --- /dev/null +++ b/test/src/services/user-service-oidc.test.js @@ -0,0 +1,145 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const config = require('../../../src/config') +const { MockOidcProvider } = require('../../support/mock-oidc-provider') +const { + snapshotOidcEnv, + restoreOidcEnv, + applyOidcEnv, + enableMockOidcTls, + restoreMockOidcTls, + reloadOidcModule +} = require('../../support/oidc-test-helpers') + +function reloadUserServiceModule () { + const userServicePath = require.resolve('../../../src/services/user-service') + delete require.cache[userServicePath] + return require('../../../src/services/user-service') +} + +describe('User service OIDC', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('provider', () => new MockOidcProvider()) + + beforeEach(async () => { + enableMockOidcTls() + await $provider.start() + applyOidcEnv($provider.getEnv()) + reloadOidcModule() + }) + + afterEach(async () => { + $sandbox.restore() + restoreOidcEnv($envSnapshot) + restoreMockOidcTls() + await $provider.stop() + }) + + describe('login()', () => { + it('returns access and refresh tokens from the issuer token endpoint', async () => { + const UserService = reloadUserServiceModule() + const result = await UserService.login({ + email: $provider.username, + password: $provider.password + }, false) + + expect(result.accessToken).to.be.a('string').that.is.not.empty + expect(result.refreshToken).to.be.a('string').that.is.not.empty + }) + + it('throws InvalidCredentialsError for bad password', async () => { + const UserService = reloadUserServiceModule() + try { + await UserService.login({ + email: $provider.username, + password: 'wrong-password' + }, false) + expect.fail('expected login to fail') + } catch (error) { + expect(error.name).to.equal('InvalidCredentialsError') + } + }) + }) + + describe('refresh()', () => { + it('returns a new access token for a valid refresh token', async () => { + const UserService = reloadUserServiceModule() + const loginResult = await UserService.login({ + email: $provider.username, + password: $provider.password + }, false) + + const refreshResult = await UserService.refresh({ + refreshToken: loginResult.refreshToken + }, false) + + expect(refreshResult.accessToken).to.be.a('string').that.is.not.empty + expect(refreshResult.refreshToken).to.be.a('string').that.is.not.empty + }) + }) + + describe('profile()', () => { + it('returns userinfo claims for a valid bearer token', async () => { + const UserService = reloadUserServiceModule() + const loginResult = await UserService.login({ + email: $provider.username, + password: $provider.password + }, false) + + const profile = await UserService.profile({ + headers: { + authorization: `Bearer ${loginResult.accessToken}` + } + }, false) + + expect(profile.preferred_username).to.equal($provider.username) + expect(profile.email).to.equal(`${$provider.username}@example.com`) + }) + }) + + describe('logout()', () => { + it('returns success after best-effort token revocation', async () => { + const UserService = reloadUserServiceModule() + const loginResult = await UserService.login({ + email: $provider.username, + password: $provider.password + }, false) + + const result = await UserService.logout({ + headers: { + authorization: `Bearer ${loginResult.accessToken}` + } + }, false) + + expect(result).to.deep.equal({ status: 'success' }) + }) + }) + + describe('dev mode without auth config', () => { + beforeEach(() => { + applyOidcEnv({}) + reloadOidcModule() + }) + + it('returns mock tokens when auth is not configured', async () => { + const originalGet = config.get.bind(config) + $sandbox.stub(config, 'get').callsFake((key, defaultValue) => { + if (key === 'server.devMode') { + return true + } + return originalGet(key, defaultValue) + }) + + const UserService = reloadUserServiceModule() + const result = await UserService.login({ + email: 'dev@example.com', + password: 'password' + }, false) + + expect(result.accessToken).to.equal('mock-access-token') + expect(result.refreshToken).to.equal('mock-refresh-token') + }) + }) +}) diff --git a/test/src/support/mock-oidc-smoke.test.js b/test/src/support/mock-oidc-smoke.test.js new file mode 100644 index 00000000..4d62a713 --- /dev/null +++ b/test/src/support/mock-oidc-smoke.test.js @@ -0,0 +1,35 @@ +const { expect } = require('chai') + +const { MockOidcProvider } = require('../../support/mock-oidc-provider') +const { + enableMockOidcTls, + restoreMockOidcTls +} = require('../../support/oidc-test-helpers') +const { verifyMockAccessToken } = require('../../support/oidc-smoke') + +describe('Mock OIDC provider smoke', () => { + def('provider', () => new MockOidcProvider()) + + beforeEach(async () => { + enableMockOidcTls() + await $provider.start() + }) + + afterEach(async () => { + restoreMockOidcTls() + await $provider.stop() + }) + + it('serves discovery metadata and validates RS256 access tokens', async () => { + const { token, payload } = await verifyMockAccessToken($provider, { + preferred_username: 'smoke-user', + roles: ['SRE'] + }) + + expect(token.split('.')).to.have.length(3) + expect(payload.preferred_username).to.equal('smoke-user') + expect(payload.roles).to.deep.equal(['SRE']) + expect(payload.iss).to.equal($provider.issuer) + expect(payload.aud).to.equal($provider.clientId) + }) +}) diff --git a/test/support/mock-oidc-provider.js b/test/support/mock-oidc-provider.js new file mode 100644 index 00000000..9c845d89 --- /dev/null +++ b/test/support/mock-oidc-provider.js @@ -0,0 +1,262 @@ +const https = require('https') +const { URL } = require('url') +const forge = require('node-forge') +const { generateKeyPair, exportJWK, SignJWT, calculateJwkThumbprint, decodeJwt } = require('jose') + +function createSelfSignedTlsCredentials () { + const keys = forge.pki.rsa.generateKeyPair(2048) + const certificate = forge.pki.createCertificate() + + certificate.publicKey = keys.publicKey + certificate.serialNumber = '01' + certificate.validity.notBefore = new Date() + certificate.validity.notAfter = new Date() + certificate.validity.notAfter.setFullYear(certificate.validity.notBefore.getFullYear() + 1) + + const attributes = [{ name: 'commonName', value: 'mock-oidc' }] + certificate.setSubject(attributes) + certificate.setIssuer(attributes) + certificate.setExtensions([{ + name: 'subjectAltName', + altNames: [{ type: 7, ip: '127.0.0.1' }] + }]) + certificate.sign(keys.privateKey, forge.md.sha256.create()) + + return { + key: forge.pki.privateKeyToPem(keys.privateKey), + cert: forge.pki.certificateToPem(certificate) + } +} + +/** + * Minimal OIDC issuer for unit tests and local dev smoke runs. + * Serves discovery, JWKS, token, userinfo, and revocation over HTTPS. + */ +class MockOidcProvider { + constructor (options = {}) { + this.clientId = options.clientId || 'controller-test-client' + this.clientSecret = options.clientSecret || 'test-client-secret' + this.username = options.username || 'test-user' + this.password = options.password || 'test-password' + this.port = options.port || 0 + this.server = null + this.baseUrl = null + this.issuer = null + this.privateKey = null + this.publicJwk = null + this.kid = null + this.tls = createSelfSignedTlsCredentials() + this.refreshTokens = new Map() + } + + async start () { + const { publicKey, privateKey } = await generateKeyPair('RS256') + this.privateKey = privateKey + this.publicJwk = await exportJWK(publicKey) + this.kid = await calculateJwkThumbprint(this.publicJwk) + this.publicJwk.kid = this.kid + this.publicJwk.alg = 'RS256' + this.publicJwk.use = 'sig' + + return new Promise((resolve, reject) => { + this.server = https.createServer(this.tls, (req, res) => { + this.handleRequest(req, res).catch((error) => { + res.statusCode = 500 + res.end(error.message) + }) + }) + this.server.on('error', reject) + this.server.listen(this.port, '127.0.0.1', () => { + const address = this.server.address() + this.baseUrl = `https://127.0.0.1:${address.port}` + this.issuer = this.baseUrl + resolve(this) + }) + }) + } + + async readBody (req) { + const chunks = [] + for await (const chunk of req) { + chunks.push(chunk) + } + return Buffer.concat(chunks).toString('utf8') + } + + async handleRequest (req, res) { + const url = new URL(req.url, this.baseUrl) + const pathname = url.pathname + + if (pathname === '/.well-known/openid-configuration') { + return this.sendJson(res, { + issuer: this.issuer, + jwks_uri: `${this.baseUrl}/jwks`, + token_endpoint: `${this.baseUrl}/token`, + userinfo_endpoint: `${this.baseUrl}/userinfo`, + revocation_endpoint: `${this.baseUrl}/revoke`, + authorization_endpoint: `${this.baseUrl}/authorize`, + response_types_supported: ['code'], + subject_types_supported: ['public'], + id_token_signing_alg_values_supported: ['RS256'] + }) + } + + if (pathname === '/jwks') { + return this.sendJson(res, { keys: [this.publicJwk] }) + } + + if (pathname === '/token' && req.method === 'POST') { + return this.handleTokenRequest(req, res) + } + + if (pathname === '/userinfo' && req.method === 'GET') { + return this.handleUserInfoRequest(req, res) + } + + if (pathname === '/revoke' && req.method === 'POST') { + res.statusCode = 200 + return res.end() + } + + res.statusCode = 404 + res.end('Not found') + } + + validateClient (params) { + return params.get('client_id') === this.clientId + && params.get('client_secret') === this.clientSecret + } + + async handleTokenRequest (req, res) { + const body = await this.readBody(req) + const params = new URLSearchParams(body) + + if (!this.validateClient(params)) { + return this.sendOAuthError(res, 401, 'invalid_client', 'Invalid client credentials') + } + + const grantType = params.get('grant_type') + + if (grantType === 'password') { + const username = params.get('username') + const password = params.get('password') + if (username !== this.username || password !== this.password) { + return this.sendOAuthError(res, 401, 'invalid_grant', 'Invalid user credentials') + } + + return this.sendTokenResponse(res, await this.buildUserClaims(username)) + } + + if (grantType === 'refresh_token') { + const refreshToken = params.get('refresh_token') + const claims = this.refreshTokens.get(refreshToken) + if (!claims) { + return this.sendOAuthError(res, 401, 'invalid_grant', 'Invalid refresh token') + } + + return this.sendTokenResponse(res, claims) + } + + return this.sendOAuthError(res, 400, 'unsupported_grant_type', 'Unsupported grant type') + } + + async handleUserInfoRequest (req, res) { + const authHeader = req.headers.authorization || '' + if (!authHeader.startsWith('Bearer ')) { + return this.sendOAuthError(res, 401, 'invalid_token', 'Missing bearer token') + } + + try { + const accessToken = authHeader.slice('Bearer '.length).trim() + const claims = decodeJwt(accessToken) + return this.sendJson(res, { + sub: claims.sub, + preferred_username: claims.preferred_username, + email: claims.email, + roles: claims.roles + }) + } catch (error) { + return this.sendOAuthError(res, 401, 'invalid_token', 'Invalid bearer token') + } + } + + async buildUserClaims (username) { + return { + sub: 'test-user-id', + preferred_username: username, + email: `${username}@example.com`, + roles: ['SRE', 'Viewer'] + } + } + + async sendTokenResponse (res, claims) { + const accessToken = await this.issueAccessToken(claims) + const refreshToken = `mock-refresh-${claims.sub}-${Date.now()}` + this.refreshTokens.set(refreshToken, claims) + + return this.sendJson(res, { + access_token: accessToken, + refresh_token: refreshToken, + token_type: 'Bearer', + expires_in: 3600 + }) + } + + sendOAuthError (res, statusCode, error, errorDescription) { + res.statusCode = statusCode + return this.sendJson(res, { + error, + error_description: errorDescription + }) + } + + sendJson (res, body) { + res.setHeader('Content-Type', 'application/json') + res.end(JSON.stringify(body)) + } + + async issueAccessToken (claims = {}) { + const now = Math.floor(Date.now() / 1000) + const payload = { + sub: 'test-user-id', + preferred_username: 'test-user', + iss: this.issuer, + aud: this.clientId, + exp: now + 3600, + iat: now, + ...claims + } + + return new SignJWT(payload) + .setProtectedHeader({ alg: 'RS256', kid: this.kid }) + .sign(this.privateKey) + } + + getEnv () { + return { + OIDC_ISSUER_URL: this.issuer, + OIDC_CLIENT_ID: this.clientId, + OIDC_CLIENT_SECRET: this.clientSecret + } + } + + async stop () { + if (!this.server) { + return + } + + if (typeof this.server.closeAllConnections === 'function') { + this.server.closeAllConnections() + } + + await new Promise((resolve, reject) => { + this.server.close((error) => (error ? reject(error) : resolve())) + }) + this.server = null + this.refreshTokens.clear() + } +} + +module.exports = { + MockOidcProvider +} diff --git a/test/support/oidc-smoke.js b/test/support/oidc-smoke.js new file mode 100644 index 00000000..157ed3db --- /dev/null +++ b/test/support/oidc-smoke.js @@ -0,0 +1,40 @@ +const { createRemoteJWKSet, jwtVerify } = require('jose') +const oidcClient = require('openid-client') + +/** + * Generic OIDC smoke helper: discovery + JWKS fetch + JWT verify against MockOidcProvider. + */ +async function verifyMockAccessToken (provider, claims = {}) { + const issuer = new URL(provider.issuer) + const configuration = await oidcClient.discovery( + issuer, + provider.clientId, + provider.clientSecret + ) + const metadata = configuration.serverMetadata() + const jwks = createRemoteJWKSet(new URL(metadata.jwks_uri)) + const token = await provider.issueAccessToken(claims) + const verifyOptions = { issuer: metadata.issuer } + if (provider.clientId) { + verifyOptions.audience = provider.clientId + } + + const { payload } = await jwtVerify(token, jwks, verifyOptions) + return { token, payload } +} + +function buildKauthGrant (payload, token) { + return { + grant: { + access_token: { + token, + content: payload + } + } + } +} + +module.exports = { + verifyMockAccessToken, + buildKauthGrant +} diff --git a/test/support/oidc-test-helpers.js b/test/support/oidc-test-helpers.js new file mode 100644 index 00000000..755e8d82 --- /dev/null +++ b/test/support/oidc-test-helpers.js @@ -0,0 +1,92 @@ +const OIDC_ENV_KEYS = [ + 'OIDC_ISSUER_URL', + 'OIDC_CLIENT_ID', + 'OIDC_CLIENT_SECRET', + 'OIDC_VIEWER_CLIENT_ID' +] + +let savedTlsRejectUnauthorized + +function snapshotOidcEnv () { + return OIDC_ENV_KEYS.reduce((env, key) => { + env[key] = process.env[key] + return env + }, {}) +} + +function restoreOidcEnv (snapshot) { + for (const key of OIDC_ENV_KEYS) { + if (snapshot[key] === undefined) { + delete process.env[key] + } else { + process.env[key] = snapshot[key] + } + } +} + +function applyOidcEnv (env = {}) { + for (const key of OIDC_ENV_KEYS) { + if (env[key] === undefined || env[key] === null) { + delete process.env[key] + } else { + process.env[key] = env[key] + } + } +} + +function enableMockOidcTls () { + savedTlsRejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' +} + +function restoreMockOidcTls () { + if (savedTlsRejectUnauthorized === undefined) { + delete process.env.NODE_TLS_REJECT_UNAUTHORIZED + } else { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = savedTlsRejectUnauthorized + } + savedTlsRejectUnauthorized = undefined +} + +function reloadOidcModule () { + const oidcPath = require.resolve('../../src/config/oidc') + delete require.cache[oidcPath] + return require('../../src/config/oidc') +} + +function runMiddleware (middleware, req) { + return new Promise((resolve, reject) => { + const res = { + statusCode: null, + body: null, + status (code) { + this.statusCode = code + return this + }, + json (payload) { + this.body = payload + resolve({ req, res: this, nextCalled: false }) + return this + } + } + + middleware(req, res, (error) => { + if (error) { + reject(error) + return + } + resolve({ req, res, nextCalled: true }) + }).catch(reject) + }) +} + +module.exports = { + OIDC_ENV_KEYS, + snapshotOidcEnv, + restoreOidcEnv, + applyOidcEnv, + enableMockOidcTls, + restoreMockOidcTls, + reloadOidcModule, + runMiddleware +} From 64f06d695a3d2ccf657d01cb2456d32ef6e590c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 10 Jun 2026 22:15:19 +0300 Subject: [PATCH 39/75] Add system image config and env mapping for architectures 3 and 4. Extend router, debug, and NATS defaults and ROUTER/DEBUG/NATS_IMAGE_* vars to match four-architecture support. --- src/config/config.yaml | 6 ++++++ src/config/env-mapping.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/config/config.yaml b/src/config/config.yaml index 33484fd1..4476811e 100644 --- a/src/config/config.yaml +++ b/src/config/config.yaml @@ -110,12 +110,18 @@ systemImages: router: "1": "ghcr.io/datasance/router:latest" "2": "ghcr.io/datasance/router:latest" + "3": "ghcr.io/datasance/router:latest" + "4": "ghcr.io/datasance/router:latest" debug: "1": "ghcr.io/datasance/node-debugger:latest" "2": "ghcr.io/datasance/node-debugger:latest" + "3": "ghcr.io/datasance/node-debugger:latest" + "4": "ghcr.io/datasance/node-debugger:latest" nats: "1": "ghcr.io/datasance/nats:latest" "2": "ghcr.io/datasance/nats:latest" + "3": "ghcr.io/datasance/nats:latest" + "4": "ghcr.io/datasance/nats:latest" # NATS Configuration nats: diff --git a/src/config/env-mapping.js b/src/config/env-mapping.js index ca7cabae..b44faa06 100644 --- a/src/config/env-mapping.js +++ b/src/config/env-mapping.js @@ -86,10 +86,16 @@ module.exports = { // System Images Configuration 'ROUTER_IMAGE_1': 'systemImages.router.1', 'ROUTER_IMAGE_2': 'systemImages.router.2', + 'ROUTER_IMAGE_3': 'systemImages.router.3', + 'ROUTER_IMAGE_4': 'systemImages.router.4', 'DEBUG_IMAGE_1': 'systemImages.debug.1', 'DEBUG_IMAGE_2': 'systemImages.debug.2', + 'DEBUG_IMAGE_3': 'systemImages.debug.3', + 'DEBUG_IMAGE_4': 'systemImages.debug.4', 'NATS_IMAGE_1': 'systemImages.nats.1', 'NATS_IMAGE_2': 'systemImages.nats.2', + 'NATS_IMAGE_3': 'systemImages.nats.3', + 'NATS_IMAGE_4': 'systemImages.nats.4', // NATS Configuration 'NATS_ENABLED': 'nats.enabled', From 78e6a84e60d89a76018713681ccb641a9a95de60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 05:40:50 +0300 Subject: [PATCH 40/75] Add oidc-provider, argon2, and otplib for embedded identity. --- package-lock.json | 798 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 7 +- 2 files changed, 802 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a5da5dfc..96105e6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@msgpack/msgpack": "^3.1.2", "@nats-io/jwt": "^0.0.10-5", "@nats-io/nkeys": "^2.0.3", + "@node-rs/argon2": "^2.0.2", "@opentelemetry/api": "^1.9.1", "@opentelemetry/exporter-trace-otlp-http": "^0.218.0", "@opentelemetry/instrumentation-express": "^0.66.0", @@ -51,7 +52,9 @@ "nconf": "0.12.1", "node-fetch-npm": "^2.0.4", "node-forge": "^1.4.0", + "oidc-provider": "^9.8.4", "openid-client": "^6.8.4", + "otplib": "^13.4.1", "pg": "8.12.0", "pino": "9.13.1", "pino-std-serializers": "7.0.0", @@ -1036,6 +1039,37 @@ "integrity": "sha512-F3MC2R/OTC+ivbsEwZw0813wdm9tXKdjw7mcxKl2cc0+9k4dptQOInMMpd8+poAKTJYATOQyyeXUVaLJPZRNUA==", "license": "EPL-2.0" }, + "node_modules/@emnapi/core": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz", + "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz", + "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -1673,6 +1707,74 @@ "jsep": "^0.4.0||^1.0.0" } }, + "node_modules/@koa/cors": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-5.0.0.tgz", + "integrity": "sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==", + "license": "MIT", + "dependencies": { + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@koa/router": { + "version": "15.6.0", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-15.6.0.tgz", + "integrity": "sha512-iEOXlvGIBqSNkGXrg0XtMARAOm5zA24oedXxiTGEkrD4JgwVjfRDddCQvW1s4WEcwDYvyecRbf8BikXsuEEj8w==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "koa-compose": "^4.1.0", + "path-to-regexp": "^8.4.2" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "koa": "^2.0.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "koa": { + "optional": false + } + } + }, + "node_modules/@koa/router/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@koa/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@koa/router/node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/@kubernetes/client-node": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-1.4.0.tgz", @@ -1706,6 +1808,18 @@ "node": ">= 18" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@nats-io/jwt": { "version": "0.0.10-5", "resolved": "https://registry.npmjs.org/@nats-io/jwt/-/jwt-0.0.10-5.tgz", @@ -1763,6 +1877,255 @@ ], "license": "MIT" }, + "node_modules/@node-rs/argon2": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-2.0.2.tgz", + "integrity": "sha512-t64wIsPEtNd4aUPuTAyeL2ubxATCBGmeluaKXEMAFk/8w6AJIVVkeLKMBpgLW6LU2t5cQxT+env/c6jxbtTQBg==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@node-rs/argon2-android-arm-eabi": "2.0.2", + "@node-rs/argon2-android-arm64": "2.0.2", + "@node-rs/argon2-darwin-arm64": "2.0.2", + "@node-rs/argon2-darwin-x64": "2.0.2", + "@node-rs/argon2-freebsd-x64": "2.0.2", + "@node-rs/argon2-linux-arm-gnueabihf": "2.0.2", + "@node-rs/argon2-linux-arm64-gnu": "2.0.2", + "@node-rs/argon2-linux-arm64-musl": "2.0.2", + "@node-rs/argon2-linux-x64-gnu": "2.0.2", + "@node-rs/argon2-linux-x64-musl": "2.0.2", + "@node-rs/argon2-wasm32-wasi": "2.0.2", + "@node-rs/argon2-win32-arm64-msvc": "2.0.2", + "@node-rs/argon2-win32-ia32-msvc": "2.0.2", + "@node-rs/argon2-win32-x64-msvc": "2.0.2" + } + }, + "node_modules/@node-rs/argon2-android-arm-eabi": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-2.0.2.tgz", + "integrity": "sha512-DV/H8p/jt40lrao5z5g6nM9dPNPGEHL+aK6Iy/og+dbL503Uj0AHLqj1Hk9aVUSCNnsDdUEKp4TVMi0YakDYKw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-android-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-2.0.2.tgz", + "integrity": "sha512-1LKwskau+8O1ktKx7TbK7jx1oMOMt4YEXZOdSNIar1TQKxm6isZ0cRXgHLibPHEcNHgYRsJWDE9zvDGBB17QDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-darwin-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-2.0.2.tgz", + "integrity": "sha512-3TTNL/7wbcpNju5YcqUrCgXnXUSbD7ogeAKatzBVHsbpjZQbNb1NDxDjqqrWoTt6XL3z9mJUMGwbAk7zQltHtA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-darwin-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-2.0.2.tgz", + "integrity": "sha512-vNPfkLj5Ij5111UTiYuwgxMqE7DRbOS2y58O2DIySzSHbcnu+nipmRKg+P0doRq6eKIJStyBK8dQi5Ic8pFyDw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-freebsd-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-2.0.2.tgz", + "integrity": "sha512-M8vQZk01qojQfCqQU0/O1j1a4zPPrz93zc9fSINY7Q/6RhQRBCYwDw7ltDCZXg5JRGlSaeS8cUXWyhPGar3cGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm-gnueabihf": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-2.0.2.tgz", + "integrity": "sha512-7EmmEPHLzcu0G2GDh30L6G48CH38roFC2dqlQJmtRCxs6no3tTE/pvgBGatTp/o2n2oyOJcfmgndVFcUpwMnww==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm64-gnu": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-2.0.2.tgz", + "integrity": "sha512-6lsYh3Ftbk+HAIZ7wNuRF4SZDtxtFTfK+HYFAQQyW7Ig3LHqasqwfUKRXVSV5tJ+xTnxjqgKzvZSUJCAyIfHew==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm64-musl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-2.0.2.tgz", + "integrity": "sha512-p3YqVMNT/4DNR67tIHTYGbedYmXxW9QlFmF39SkXyEbGQwpgSf6pH457/fyXBIYznTU/smnG9EH+C1uzT5j4hA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-x64-gnu": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-2.0.2.tgz", + "integrity": "sha512-ZM3jrHuJ0dKOhvA80gKJqBpBRmTJTFSo2+xVZR+phQcbAKRlDMSZMFDiKbSTnctkfwNFtjgDdh5g1vaEV04AvA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-x64-musl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-2.0.2.tgz", + "integrity": "sha512-of5uPqk7oCRF/44a89YlWTEfjsftPywyTULwuFDKyD8QtVZoonrJR6ZWvfFE/6jBT68S0okAkAzzMEdBVWdxWw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-wasm32-wasi": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-2.0.2.tgz", + "integrity": "sha512-U3PzLYKSQYzTERstgtHLd4ZTkOF9co57zTXT77r0cVUsleGZOrd6ut7rHzeWwoJSiHOVxxa0OhG1JVQeB7lLoQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@node-rs/argon2-win32-arm64-msvc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-2.0.2.tgz", + "integrity": "sha512-Eisd7/NM0m23ijrGr6xI2iMocdOuyl6gO27gfMfya4C5BODbUSP7ljKJ7LrA0teqZMdYHesRDzx36Js++/vhiQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-win32-ia32-msvc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-2.0.2.tgz", + "integrity": "sha512-GsE2ezwAYwh72f9gIjbGTZOf4HxEksb5M2eCaj+Y5rGYVwAdt7C12Q2e9H5LRYxWcFvLH4m4jiSZpQQ4upnPAQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-win32-x64-msvc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-x64-msvc/-/argon2-win32-x64-msvc-2.0.2.tgz", + "integrity": "sha512-cJxWXanH4Ew9CfuZ4IAEiafpOBCe97bzoKowHCGk5lG/7kR4WF/eknnBlHW9m8q7t10mKq75kruPLtbSDqgRTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@one-ini/wasm": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", @@ -2566,6 +2929,74 @@ "node": ">=14" } }, + "node_modules/@otplib/core": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-13.4.1.tgz", + "integrity": "sha512-KIXgK1hNtWJEBMTastbe1bpmuais+3f+ATeO8TkMs2rNkfGO1FbQy8+/UWVEu3TR/iTJerU0idkPudaPmLP2BA==", + "license": "MIT" + }, + "node_modules/@otplib/hotp": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/hotp/-/hotp-13.4.1.tgz", + "integrity": "sha512-g9q04SwpG5ZtMnVkUcgcoAlwCH4YLROZN1qhyBwgkBzqYYVSYhpP6gSGaxGHwePLt1c+e6NqDlgIZN+e1/XPuA==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.4.1", + "@otplib/uri": "13.4.1" + } + }, + "node_modules/@otplib/plugin-base32-scure": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.4.1.tgz", + "integrity": "sha512-Fs/r5qisC05SRhT6xWXaypB6PVC0vgWf6zztmi0J5RnQ09OJiPDWCJFH6cDm6ANsrdvB9di7X+Jb7L13BoEbUA==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.4.1", + "@scure/base": "^2.2.0" + } + }, + "node_modules/@otplib/plugin-crypto-noble": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.4.1.tgz", + "integrity": "sha512-PJfVW8/1hdS6CfxLheKPZSLTwDq4TijZbN4yRjxlv0ODdzmxpM+wGwWr1JXMdy0xJPxLziydQD5gdVqrR4/gAg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.2.0", + "@otplib/core": "13.4.1" + } + }, + "node_modules/@otplib/plugin-crypto-noble/node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@otplib/totp": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/totp/-/totp-13.4.1.tgz", + "integrity": "sha512-QOkBVPrf6AM4qZaReZPSk9/I8ATVdZpIISJz115MqeVtcrbcr5llPZ0J7804tpnjnp1vCRkI5Qjd47HhgVteBQ==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.4.1", + "@otplib/hotp": "13.4.1", + "@otplib/uri": "13.4.1" + } + }, + "node_modules/@otplib/uri": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/uri/-/uri-13.4.1.tgz", + "integrity": "sha512-xaIm7bvICMhoB2rZIR5luiaMdssWR5nY5nXnR1fdezUgZuEO58D6zrGzLp7pQuBmlpmL0HagnscDQFoskp9yiA==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.4.1" + } + }, "node_modules/@paralleldrive/cuid2": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", @@ -2692,6 +3123,15 @@ "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", "license": "BSD-3-Clause" }, + "node_modules/@scure/base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.2.0.tgz", + "integrity": "sha512-b8XEupJibegiXV+tDUseI8oLQc8ei3d/4Jkb2RpbHh3MfE054ov3uIz2dhFkB3FI8iwYkEh0gGCApkrYggkPNg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@sentry-internal/tracing": { "version": "7.120.4", "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.120.4.tgz", @@ -2946,6 +3386,16 @@ "node": ">=14.0.0" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/chai": { "version": "4.3.20", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", @@ -3081,6 +3531,7 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -4719,6 +5170,19 @@ "dev": true, "license": "MIT" }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -4921,6 +5385,12 @@ "node": ">=6" } }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "license": "MIT" + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -5091,6 +5561,12 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -6079,6 +6555,18 @@ "node": ">=0.10.0" } }, + "node_modules/eta": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-4.6.0.tgz", + "integrity": "sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/bgub/eta?sponsor=1" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -7538,6 +8026,53 @@ "dev": true, "license": "MIT" }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "license": "MIT", + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-assert/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-assert/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -9034,7 +9569,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -9242,6 +9776,18 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "license": "MIT", + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -9252,6 +9798,119 @@ "json-buffer": "3.0.1" } }, + "node_modules/koa": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa/-/koa-3.2.1.tgz", + "integrity": "sha512-e7IpWJrnanNUroVK2taAgMxoEZvHLXdQiNjeExSu/DEIWm83jaKGBgb7tLmu2rMYpA027qFB3iLR/k3AVpFRnA==", + "license": "MIT", + "dependencies": { + "accepts": "^1.3.8", + "content-disposition": "~1.0.1", + "content-type": "^1.0.5", + "cookies": "~0.9.1", + "delegates": "^1.0.0", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.5.0", + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "license": "MIT" + }, + "node_modules/koa/node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/koa/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/koa/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/koa/node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/koa/node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -10008,6 +10667,24 @@ "node": ">=8.0.0" } }, + "node_modules/nanoid": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.11.tgz", + "integrity": "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", @@ -10804,6 +11481,90 @@ "node": ">= 0.4" } }, + "node_modules/oidc-provider": { + "version": "9.8.4", + "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-9.8.4.tgz", + "integrity": "sha512-i8qe+wvhUQ7BSj6DxssIFAdpREouuqK91j2jGdAN78NIxTB5rxvU4ZniXELhUHIM4mzABeSGlp5wE6WTa8CY1Q==", + "license": "MIT", + "dependencies": { + "@koa/cors": "^5.0.0", + "@koa/router": "^15.5.0", + "debug": "^4.4.3", + "eta": "^4.6.0", + "jose": "^6.2.3", + "jsesc": "^3.1.0", + "koa": "^3.2.1", + "nanoid": "^5.1.11", + "quick-lru": "^7.3.0", + "raw-body": "^3.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/oidc-provider/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/oidc-provider/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/oidc-provider/node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/oidc-provider/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/oidc-provider/node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/on-error": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/on-error/-/on-error-2.1.0.tgz", @@ -10930,6 +11691,20 @@ "node": ">=0.10.0" } }, + "node_modules/otplib": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-13.4.1.tgz", + "integrity": "sha512-o5CxfDw6bh7hoDv0NUUIcc0RqzJ9ipfUrzeKheKJ+vs4rXZnDlA9n4a/7R1cDjpmLjKLix4BgNVRmoDkm5rLSQ==", + "license": "MIT", + "dependencies": { + "@otplib/core": "13.4.1", + "@otplib/hotp": "13.4.1", + "@otplib/plugin-base32-scure": "13.4.1", + "@otplib/plugin-crypto-noble": "13.4.1", + "@otplib/totp": "13.4.1", + "@otplib/uri": "13.4.1" + } + }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -12284,6 +13059,18 @@ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", "license": "MIT" }, + "node_modules/quick-lru": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.3.0.tgz", + "integrity": "sha512-k9lSsjl36EJdK7I06v7APZCbyGT2vMTsYSRX1Q2nbYmnkBqgUhRkAuzH08Ciotteu/PLJmIF2+tti7o3C/ts2g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -15343,6 +16130,15 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "license": "MIT", + "engines": { + "node": ">=0.6.x" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/package.json b/package.json index 0515300a..6c9ff401 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "@msgpack/msgpack": "^3.1.2", "@nats-io/jwt": "^0.0.10-5", "@nats-io/nkeys": "^2.0.3", + "@node-rs/argon2": "^2.0.2", "@opentelemetry/api": "^1.9.1", "@opentelemetry/exporter-trace-otlp-http": "^0.218.0", "@opentelemetry/instrumentation-express": "^0.66.0", @@ -95,21 +96,23 @@ "jose": "^4.15.9", "js-yaml": "4.1.1", "jsonschema": "1.5.0", - "openid-client": "^6.8.4", "moment": "2.30.1", "multer": "1.4.5-lts.1", "mysql2": "3.10.1", "nconf": "0.12.1", "node-fetch-npm": "^2.0.4", "node-forge": "^1.4.0", + "oidc-provider": "^9.8.4", + "openid-client": "^6.8.4", + "otplib": "^13.4.1", "pg": "8.12.0", "pino": "9.13.1", "pino-std-serializers": "7.0.0", "portscanner": "2.2.0", - "sqlite3": "^6.0.1", "qs": "6.15.2", "rhea": "^3.0.4", "sequelize": "6.37.8", + "sqlite3": "^6.0.1", "string-format": "2.0.0", "ws": "^8.21.0", "xss-clean": "0.1.1" From 655f2b34090950c503f50934137e006f558fee81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 05:40:56 +0300 Subject: [PATCH 41/75] Add v3.8 auth schema, Sequelize models, and OIDC persistence adapter. --- src/data/adapters/oidc-provider-adapter.js | 140 +++++++++++++++++ src/data/migrations/README.md | 2 +- .../mysql/db_migration_mysql_v3.8.0.sql | 148 ++++++++++++++++++ .../postgres/db_migration_pg_v3.8.0.sql | 148 ++++++++++++++++++ .../sqlite/db_migration_sqlite_v3.8.0.sql | 148 ++++++++++++++++++ src/data/models/authBootstrapMeta.js | 40 +++++ src/data/models/authGroup.js | 49 ++++++ src/data/models/authMfa.js | 55 +++++++ src/data/models/authOidcClient.js | 42 +++++ src/data/models/authOidcKey.js | 45 ++++++ src/data/models/authOidcProviderState.js | 72 +++++++++ src/data/models/authPasswordResetSession.js | 44 ++++++ src/data/models/authPolicy.js | 90 +++++++++++ src/data/models/authRefreshToken.js | 62 ++++++++ src/data/models/authUser.js | 105 +++++++++++++ src/data/models/authUserGroup.js | 54 +++++++ src/data/providers/database-provider.js | 27 ++-- .../seeders/mysql/db_seeder_mysql_v3.8.0.sql | 27 ++++ .../seeders/postgres/db_seeder_pg_v3.8.0.sql | 30 ++++ .../sqlite/db_seeder_sqlite_v3.8.0.sql | 27 ++++ 20 files changed, 1339 insertions(+), 16 deletions(-) create mode 100644 src/data/adapters/oidc-provider-adapter.js create mode 100644 src/data/models/authBootstrapMeta.js create mode 100644 src/data/models/authGroup.js create mode 100644 src/data/models/authMfa.js create mode 100644 src/data/models/authOidcClient.js create mode 100644 src/data/models/authOidcKey.js create mode 100644 src/data/models/authOidcProviderState.js create mode 100644 src/data/models/authPasswordResetSession.js create mode 100644 src/data/models/authPolicy.js create mode 100644 src/data/models/authRefreshToken.js create mode 100644 src/data/models/authUser.js create mode 100644 src/data/models/authUserGroup.js diff --git a/src/data/adapters/oidc-provider-adapter.js b/src/data/adapters/oidc-provider-adapter.js new file mode 100644 index 00000000..e2a28464 --- /dev/null +++ b/src/data/adapters/oidc-provider-adapter.js @@ -0,0 +1,140 @@ +'use strict' + +const { Op } = require('sequelize') + +function isExpired (expiresAt) { + return expiresAt && expiresAt <= new Date() +} + +function rowToPayload (row) { + const payload = JSON.parse(row.payload) + if (row.consumed) { + payload.consumed = row.consumedAt || true + } + return payload +} + +class OidcProviderAdapter { + constructor (name, getStateModel) { + this.name = name + this.getStateModel = getStateModel + } + + async upsert (id, payload, expiresIn) { + const AuthOidcProviderState = this.getStateModel() + const expiresAt = expiresIn ? new Date(Date.now() + expiresIn * 1000) : null + + await AuthOidcProviderState.upsert({ + model: this.name, + recordId: id, + payload: JSON.stringify(payload), + expiresAt, + grantId: payload.grantId || null, + uid: payload.uid || null, + userCode: payload.userCode || null, + consumed: false, + consumedAt: null + }) + } + + async find (id) { + const AuthOidcProviderState = this.getStateModel() + const row = await AuthOidcProviderState.findOne({ + where: { + model: this.name, + recordId: id + } + }) + + if (!row || isExpired(row.expiresAt)) { + return undefined + } + + return rowToPayload(row) + } + + async findByUserCode (userCode) { + const AuthOidcProviderState = this.getStateModel() + const row = await AuthOidcProviderState.findOne({ + where: { + model: this.name, + userCode + } + }) + + if (!row || isExpired(row.expiresAt)) { + return undefined + } + + return rowToPayload(row) + } + + async findByUid (uid) { + const AuthOidcProviderState = this.getStateModel() + const row = await AuthOidcProviderState.findOne({ + where: { + model: this.name, + uid + } + }) + + if (!row || isExpired(row.expiresAt)) { + return undefined + } + + return rowToPayload(row) + } + + async consume (id) { + const AuthOidcProviderState = this.getStateModel() + await AuthOidcProviderState.update({ + consumed: true, + consumedAt: new Date() + }, { + where: { + model: this.name, + recordId: id + } + }) + } + + async destroy (id) { + const AuthOidcProviderState = this.getStateModel() + await AuthOidcProviderState.destroy({ + where: { + model: this.name, + recordId: id + } + }) + } + + async revokeByGrantId (grantId) { + const AuthOidcProviderState = this.getStateModel() + await AuthOidcProviderState.destroy({ + where: { + grantId + } + }) + } +} + +function createOidcProviderAdapterFactory (getStateModel) { + return (name) => new OidcProviderAdapter(name, getStateModel) +} + +async function purgeExpiredOidcProviderStates (getStateModel) { + const AuthOidcProviderState = getStateModel() + await AuthOidcProviderState.destroy({ + where: { + expiresAt: { + [Op.lte]: new Date() + } + } + }) +} + +module.exports = { + OidcProviderAdapter, + createOidcProviderAdapterFactory, + purgeExpiredOidcProviderStates +} diff --git a/src/data/migrations/README.md b/src/data/migrations/README.md index d310cabc..8347fb78 100644 --- a/src/data/migrations/README.md +++ b/src/data/migrations/README.md @@ -4,7 +4,7 @@ New installations use **`db_migration_*_v3.8.0.sql`** and **`db_seeder_*_v3.8.0.sql`** (sqlite, mysql, postgres). The migration runner in `src/data/providers/database-provider.js` -records schema version **`3.8.0`**. +records schema version **`3.8.0`** (includes embedded auth tables). Greenfield only: wipe the data directory or database before upgrading from pre-3.8 builds. There is no v3.7→v3.8 incremental migrator. diff --git a/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql b/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql index e6d7b4c8..9ce38476 100644 --- a/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql +++ b/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql @@ -970,4 +970,152 @@ CREATE INDEX idx_microservices_nats_rule_id ON Microservices (nats_rule_id); CREATE INDEX idx_microservices_nats_account_id ON Microservices (nats_account_id); CREATE INDEX idx_microservices_nats_user_id ON Microservices (nats_user_id); +CREATE TABLE IF NOT EXISTS AuthUsers ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + password_history_hashes TEXT, + must_change_password BOOLEAN DEFAULT false, + is_bootstrap BOOLEAN DEFAULT false, + failed_attempts INT DEFAULT 0, + locked_until DATETIME, + deleted_at DATETIME, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_users_email ON AuthUsers (email); +CREATE INDEX idx_auth_users_deleted_at ON AuthUsers (deleted_at); + +CREATE TABLE IF NOT EXISTS AuthGroups ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL UNIQUE, + is_system BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS AuthUserGroups ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id VARCHAR(36) NOT NULL, + group_id INT NOT NULL, + created_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE, + FOREIGN KEY (group_id) REFERENCES AuthGroups (id) ON DELETE CASCADE, + UNIQUE KEY uk_auth_user_groups_user_group (user_id, group_id) +); + +CREATE INDEX idx_auth_user_groups_user_id ON AuthUserGroups (user_id); +CREATE INDEX idx_auth_user_groups_group_id ON AuthUserGroups (group_id); + +CREATE TABLE IF NOT EXISTS AuthMfa ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id VARCHAR(36) NOT NULL UNIQUE, + totp_secret_encrypted TEXT, + enabled BOOLEAN DEFAULT false, + recovery_codes_hash TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_mfa_user_id ON AuthMfa (user_id); + +CREATE TABLE IF NOT EXISTS AuthPasswordResetSessions ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + user_id VARCHAR(36) NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_password_reset_sessions_user_id ON AuthPasswordResetSessions (user_id); +CREATE INDEX idx_auth_password_reset_sessions_expires_at ON AuthPasswordResetSessions (expires_at); + +CREATE TABLE IF NOT EXISTS AuthRefreshTokens ( + id INT AUTO_INCREMENT PRIMARY KEY, + token_hash VARCHAR(255) NOT NULL, + user_id VARCHAR(36) NOT NULL, + family_id VARCHAR(36) NOT NULL, + expires_at DATETIME NOT NULL, + revoked BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_refresh_tokens_token_hash ON AuthRefreshTokens (token_hash); +CREATE INDEX idx_auth_refresh_tokens_user_id ON AuthRefreshTokens (user_id); +CREATE INDEX idx_auth_refresh_tokens_family_id ON AuthRefreshTokens (family_id); +CREATE INDEX idx_auth_refresh_tokens_expires_at ON AuthRefreshTokens (expires_at); + +CREATE TABLE IF NOT EXISTS AuthOidcKeys ( + id INT AUTO_INCREMENT PRIMARY KEY, + kid VARCHAR(255) NOT NULL UNIQUE, + key_material_encrypted TEXT, + vault_ref TEXT, + active BOOLEAN DEFAULT true, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_oidc_keys_active ON AuthOidcKeys (active); + +CREATE TABLE IF NOT EXISTS AuthOidcClients ( + id INT AUTO_INCREMENT PRIMARY KEY, + client_id VARCHAR(255) NOT NULL UNIQUE, + secret_ref TEXT, + client_type VARCHAR(32) NOT NULL DEFAULT 'confidential', + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS AuthOidcProviderStates ( + id INT AUTO_INCREMENT PRIMARY KEY, + model VARCHAR(64) NOT NULL, + record_id VARCHAR(255) NOT NULL, + payload TEXT NOT NULL, + expires_at DATETIME, + grant_id VARCHAR(255), + uid VARCHAR(255), + user_code VARCHAR(255), + consumed BOOLEAN DEFAULT false, + consumed_at DATETIME, + created_at DATETIME, + updated_at DATETIME, + UNIQUE KEY uq_auth_oidc_provider_states_model_record (model, record_id) +); + +CREATE INDEX idx_auth_oidc_provider_states_grant_id ON AuthOidcProviderStates (grant_id); +CREATE INDEX idx_auth_oidc_provider_states_uid ON AuthOidcProviderStates (uid); +CREATE INDEX idx_auth_oidc_provider_states_user_code ON AuthOidcProviderStates (user_code); +CREATE INDEX idx_auth_oidc_provider_states_expires_at ON AuthOidcProviderStates (expires_at); + +CREATE TABLE IF NOT EXISTS AuthBootstrapMeta ( + id INT AUTO_INCREMENT PRIMARY KEY, + completed_at DATETIME, + bootstrap_admin_user_id VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (bootstrap_admin_user_id) REFERENCES AuthUsers (id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS AuthPolicy ( + id INT PRIMARY KEY, + min_password_length INT DEFAULT 12, + require_uppercase BOOLEAN DEFAULT true, + require_lowercase BOOLEAN DEFAULT true, + require_digit BOOLEAN DEFAULT true, + password_max_age_days INT DEFAULT 0, + password_history_count INT DEFAULT 5, + max_failed_attempts INT DEFAULT 5, + lockout_duration_minutes INT DEFAULT 15, + access_token_ttl_seconds INT DEFAULT 900, + refresh_token_ttl_seconds INT DEFAULT 604800, + refresh_rotation BOOLEAN DEFAULT true, + max_concurrent_sessions INT, + created_at DATETIME, + updated_at DATETIME +); + COMMIT; diff --git a/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql b/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql index 1db1b169..4ae51e65 100644 --- a/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql +++ b/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql @@ -961,3 +961,151 @@ CREATE INDEX idx_applications_nats_rule_id ON "Applications" (nats_rule_id); CREATE INDEX idx_microservices_nats_rule_id ON "Microservices" (nats_rule_id); CREATE INDEX idx_microservices_nats_account_id ON "Microservices" (nats_account_id); CREATE INDEX idx_microservices_nats_user_id ON "Microservices" (nats_user_id); + +CREATE TABLE IF NOT EXISTS "AuthUsers" ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + password_history_hashes TEXT, + must_change_password BOOLEAN DEFAULT false, + is_bootstrap BOOLEAN DEFAULT false, + failed_attempts INT DEFAULT 0, + locked_until TIMESTAMP(0), + deleted_at TIMESTAMP(0), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_auth_users_email ON "AuthUsers" (email); +CREATE INDEX idx_auth_users_deleted_at ON "AuthUsers" (deleted_at); + +CREATE TABLE IF NOT EXISTS "AuthGroups" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name VARCHAR(255) NOT NULL UNIQUE, + is_system BOOLEAN DEFAULT false, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "AuthUserGroups" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + user_id VARCHAR(36) NOT NULL, + group_id INT NOT NULL, + created_at TIMESTAMP(0), + FOREIGN KEY (user_id) REFERENCES "AuthUsers" (id) ON DELETE CASCADE, + FOREIGN KEY (group_id) REFERENCES "AuthGroups" (id) ON DELETE CASCADE, + UNIQUE (user_id, group_id) +); + +CREATE INDEX idx_auth_user_groups_user_id ON "AuthUserGroups" (user_id); +CREATE INDEX idx_auth_user_groups_group_id ON "AuthUserGroups" (group_id); + +CREATE TABLE IF NOT EXISTS "AuthMfa" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + user_id VARCHAR(36) NOT NULL UNIQUE, + totp_secret_encrypted TEXT, + enabled BOOLEAN DEFAULT false, + recovery_codes_hash TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (user_id) REFERENCES "AuthUsers" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_mfa_user_id ON "AuthMfa" (user_id); + +CREATE TABLE IF NOT EXISTS "AuthPasswordResetSessions" ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + user_id VARCHAR(36) NOT NULL, + expires_at TIMESTAMP(0) NOT NULL, + created_at TIMESTAMP(0), + FOREIGN KEY (user_id) REFERENCES "AuthUsers" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_password_reset_sessions_user_id ON "AuthPasswordResetSessions" (user_id); +CREATE INDEX idx_auth_password_reset_sessions_expires_at ON "AuthPasswordResetSessions" (expires_at); + +CREATE TABLE IF NOT EXISTS "AuthRefreshTokens" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + token_hash VARCHAR(255) NOT NULL, + user_id VARCHAR(36) NOT NULL, + family_id VARCHAR(36) NOT NULL, + expires_at TIMESTAMP(0) NOT NULL, + revoked BOOLEAN DEFAULT false, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (user_id) REFERENCES "AuthUsers" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_refresh_tokens_token_hash ON "AuthRefreshTokens" (token_hash); +CREATE INDEX idx_auth_refresh_tokens_user_id ON "AuthRefreshTokens" (user_id); +CREATE INDEX idx_auth_refresh_tokens_family_id ON "AuthRefreshTokens" (family_id); +CREATE INDEX idx_auth_refresh_tokens_expires_at ON "AuthRefreshTokens" (expires_at); + +CREATE TABLE IF NOT EXISTS "AuthOidcKeys" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + kid VARCHAR(255) NOT NULL UNIQUE, + key_material_encrypted TEXT, + vault_ref TEXT, + active BOOLEAN DEFAULT true, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_auth_oidc_keys_active ON "AuthOidcKeys" (active); + +CREATE TABLE IF NOT EXISTS "AuthOidcClients" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + client_id VARCHAR(255) NOT NULL UNIQUE, + secret_ref TEXT, + client_type VARCHAR(32) NOT NULL DEFAULT 'confidential', + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "AuthOidcProviderStates" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + model VARCHAR(64) NOT NULL, + record_id VARCHAR(255) NOT NULL, + payload TEXT NOT NULL, + expires_at TIMESTAMP(0), + grant_id VARCHAR(255), + uid VARCHAR(255), + user_code VARCHAR(255), + consumed BOOLEAN DEFAULT false, + consumed_at TIMESTAMP(0), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + UNIQUE (model, record_id) +); + +CREATE INDEX idx_auth_oidc_provider_states_grant_id ON "AuthOidcProviderStates" (grant_id); +CREATE INDEX idx_auth_oidc_provider_states_uid ON "AuthOidcProviderStates" (uid); +CREATE INDEX idx_auth_oidc_provider_states_user_code ON "AuthOidcProviderStates" (user_code); +CREATE INDEX idx_auth_oidc_provider_states_expires_at ON "AuthOidcProviderStates" (expires_at); + +CREATE TABLE IF NOT EXISTS "AuthBootstrapMeta" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + completed_at TIMESTAMP(0), + bootstrap_admin_user_id VARCHAR(36), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (bootstrap_admin_user_id) REFERENCES "AuthUsers" (id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS "AuthPolicy" ( + id INT PRIMARY KEY NOT NULL CHECK (id = 1), + min_password_length INT DEFAULT 12, + require_uppercase BOOLEAN DEFAULT true, + require_lowercase BOOLEAN DEFAULT true, + require_digit BOOLEAN DEFAULT true, + password_max_age_days INT DEFAULT 0, + password_history_count INT DEFAULT 5, + max_failed_attempts INT DEFAULT 5, + lockout_duration_minutes INT DEFAULT 15, + access_token_ttl_seconds INT DEFAULT 900, + refresh_token_ttl_seconds INT DEFAULT 604800, + refresh_rotation BOOLEAN DEFAULT true, + max_concurrent_sessions INT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); diff --git a/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql b/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql index 8ea641f3..0372ace7 100644 --- a/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql +++ b/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql @@ -960,3 +960,151 @@ CREATE INDEX idx_applications_nats_rule_id ON Applications (nats_rule_id); CREATE INDEX idx_microservices_nats_rule_id ON Microservices (nats_rule_id); CREATE INDEX idx_microservices_nats_account_id ON Microservices (nats_account_id); CREATE INDEX idx_microservices_nats_user_id ON Microservices (nats_user_id); + +CREATE TABLE IF NOT EXISTS AuthUsers ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + password_history_hashes TEXT, + must_change_password BOOLEAN DEFAULT false, + is_bootstrap BOOLEAN DEFAULT false, + failed_attempts INT DEFAULT 0, + locked_until DATETIME, + deleted_at DATETIME, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_users_email ON AuthUsers (email); +CREATE INDEX idx_auth_users_deleted_at ON AuthUsers (deleted_at); + +CREATE TABLE IF NOT EXISTS AuthGroups ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name VARCHAR(255) NOT NULL UNIQUE, + is_system BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS AuthUserGroups ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + user_id VARCHAR(36) NOT NULL, + group_id INT NOT NULL, + created_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE, + FOREIGN KEY (group_id) REFERENCES AuthGroups (id) ON DELETE CASCADE, + UNIQUE (user_id, group_id) +); + +CREATE INDEX idx_auth_user_groups_user_id ON AuthUserGroups (user_id); +CREATE INDEX idx_auth_user_groups_group_id ON AuthUserGroups (group_id); + +CREATE TABLE IF NOT EXISTS AuthMfa ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + user_id VARCHAR(36) NOT NULL UNIQUE, + totp_secret_encrypted TEXT, + enabled BOOLEAN DEFAULT false, + recovery_codes_hash TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_mfa_user_id ON AuthMfa (user_id); + +CREATE TABLE IF NOT EXISTS AuthPasswordResetSessions ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + user_id VARCHAR(36) NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_password_reset_sessions_user_id ON AuthPasswordResetSessions (user_id); +CREATE INDEX idx_auth_password_reset_sessions_expires_at ON AuthPasswordResetSessions (expires_at); + +CREATE TABLE IF NOT EXISTS AuthRefreshTokens ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + token_hash VARCHAR(255) NOT NULL, + user_id VARCHAR(36) NOT NULL, + family_id VARCHAR(36) NOT NULL, + expires_at DATETIME NOT NULL, + revoked BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_refresh_tokens_token_hash ON AuthRefreshTokens (token_hash); +CREATE INDEX idx_auth_refresh_tokens_user_id ON AuthRefreshTokens (user_id); +CREATE INDEX idx_auth_refresh_tokens_family_id ON AuthRefreshTokens (family_id); +CREATE INDEX idx_auth_refresh_tokens_expires_at ON AuthRefreshTokens (expires_at); + +CREATE TABLE IF NOT EXISTS AuthOidcKeys ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + kid VARCHAR(255) NOT NULL UNIQUE, + key_material_encrypted TEXT, + vault_ref TEXT, + active BOOLEAN DEFAULT true, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_oidc_keys_active ON AuthOidcKeys (active); + +CREATE TABLE IF NOT EXISTS AuthOidcClients ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + client_id VARCHAR(255) NOT NULL UNIQUE, + secret_ref TEXT, + client_type VARCHAR(32) NOT NULL DEFAULT 'confidential', + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS AuthOidcProviderStates ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + model VARCHAR(64) NOT NULL, + record_id VARCHAR(255) NOT NULL, + payload TEXT NOT NULL, + expires_at DATETIME, + grant_id VARCHAR(255), + uid VARCHAR(255), + user_code VARCHAR(255), + consumed BOOLEAN DEFAULT false, + consumed_at DATETIME, + created_at DATETIME, + updated_at DATETIME, + UNIQUE (model, record_id) +); + +CREATE INDEX idx_auth_oidc_provider_states_grant_id ON AuthOidcProviderStates (grant_id); +CREATE INDEX idx_auth_oidc_provider_states_uid ON AuthOidcProviderStates (uid); +CREATE INDEX idx_auth_oidc_provider_states_user_code ON AuthOidcProviderStates (user_code); +CREATE INDEX idx_auth_oidc_provider_states_expires_at ON AuthOidcProviderStates (expires_at); + +CREATE TABLE IF NOT EXISTS AuthBootstrapMeta ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + completed_at DATETIME, + bootstrap_admin_user_id VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (bootstrap_admin_user_id) REFERENCES AuthUsers (id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS AuthPolicy ( + id INT PRIMARY KEY NOT NULL CHECK (id = 1), + min_password_length INT DEFAULT 12, + require_uppercase BOOLEAN DEFAULT true, + require_lowercase BOOLEAN DEFAULT true, + require_digit BOOLEAN DEFAULT true, + password_max_age_days INT DEFAULT 0, + password_history_count INT DEFAULT 5, + max_failed_attempts INT DEFAULT 5, + lockout_duration_minutes INT DEFAULT 15, + access_token_ttl_seconds INT DEFAULT 900, + refresh_token_ttl_seconds INT DEFAULT 604800, + refresh_rotation BOOLEAN DEFAULT true, + max_concurrent_sessions INT, + created_at DATETIME, + updated_at DATETIME +); diff --git a/src/data/models/authBootstrapMeta.js b/src/data/models/authBootstrapMeta.js new file mode 100644 index 00000000..06041b42 --- /dev/null +++ b/src/data/models/authBootstrapMeta.js @@ -0,0 +1,40 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthBootstrapMeta = sequelize.define('AuthBootstrapMeta', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + completedAt: { + type: DataTypes.DATE, + allowNull: true, + field: 'completed_at' + }, + bootstrapAdminUserId: { + type: DataTypes.STRING(36), + allowNull: true, + field: 'bootstrap_admin_user_id' + } + }, { + tableName: 'AuthBootstrapMeta', + timestamps: true, + underscored: true + }) + + AuthBootstrapMeta.associate = function (models) { + AuthBootstrapMeta.belongsTo(models.AuthUser, { + foreignKey: { + name: 'bootstrapAdminUserId', + field: 'bootstrap_admin_user_id' + }, + as: 'bootstrapAdminUser', + onDelete: 'SET NULL' + }) + } + + return AuthBootstrapMeta +} diff --git a/src/data/models/authGroup.js b/src/data/models/authGroup.js new file mode 100644 index 00000000..8fcb9a51 --- /dev/null +++ b/src/data/models/authGroup.js @@ -0,0 +1,49 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthGroup = sequelize.define('AuthGroup', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + name: { + type: DataTypes.STRING(255), + allowNull: false, + unique: true, + field: 'name' + }, + isSystem: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'is_system' + } + }, { + tableName: 'AuthGroups', + timestamps: true, + underscored: true, + indexes: [ + { unique: true, fields: ['name'] } + ] + }) + + AuthGroup.associate = function (models) { + AuthGroup.belongsToMany(models.AuthUser, { + through: models.AuthUserGroup, + foreignKey: { + name: 'groupId', + field: 'group_id' + }, + otherKey: { + name: 'userId', + field: 'user_id' + }, + as: 'users' + }) + } + + return AuthGroup +} diff --git a/src/data/models/authMfa.js b/src/data/models/authMfa.js new file mode 100644 index 00000000..5492f058 --- /dev/null +++ b/src/data/models/authMfa.js @@ -0,0 +1,55 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthMfa = sequelize.define('AuthMfa', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + userId: { + type: DataTypes.STRING(36), + allowNull: false, + unique: true, + field: 'user_id' + }, + totpSecretEncrypted: { + type: DataTypes.TEXT, + allowNull: true, + field: 'totp_secret_encrypted' + }, + enabled: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'enabled' + }, + recoveryCodesHash: { + type: DataTypes.TEXT, + allowNull: true, + field: 'recovery_codes_hash' + } + }, { + tableName: 'AuthMfa', + timestamps: true, + underscored: true, + indexes: [ + { unique: true, fields: ['user_id'] } + ] + }) + + AuthMfa.associate = function (models) { + AuthMfa.belongsTo(models.AuthUser, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'user', + onDelete: 'CASCADE' + }) + } + + return AuthMfa +} diff --git a/src/data/models/authOidcClient.js b/src/data/models/authOidcClient.js new file mode 100644 index 00000000..bf44e890 --- /dev/null +++ b/src/data/models/authOidcClient.js @@ -0,0 +1,42 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthOidcClient = sequelize.define('AuthOidcClient', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + clientId: { + type: DataTypes.STRING(255), + allowNull: false, + unique: true, + field: 'client_id' + }, + secretRef: { + type: DataTypes.TEXT, + allowNull: true, + field: 'secret_ref' + }, + clientType: { + type: DataTypes.STRING(32), + allowNull: false, + defaultValue: 'confidential', + field: 'client_type', + validate: { + isIn: [['confidential', 'public']] + } + } + }, { + tableName: 'AuthOidcClients', + timestamps: true, + underscored: true, + indexes: [ + { unique: true, fields: ['client_id'] } + ] + }) + + return AuthOidcClient +} diff --git a/src/data/models/authOidcKey.js b/src/data/models/authOidcKey.js new file mode 100644 index 00000000..bbcbdf79 --- /dev/null +++ b/src/data/models/authOidcKey.js @@ -0,0 +1,45 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthOidcKey = sequelize.define('AuthOidcKey', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + kid: { + type: DataTypes.STRING(255), + allowNull: false, + unique: true, + field: 'kid' + }, + keyMaterialEncrypted: { + type: DataTypes.TEXT, + allowNull: true, + field: 'key_material_encrypted' + }, + vaultRef: { + type: DataTypes.TEXT, + allowNull: true, + field: 'vault_ref' + }, + active: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + field: 'active' + } + }, { + tableName: 'AuthOidcKeys', + timestamps: true, + underscored: true, + indexes: [ + { unique: true, fields: ['kid'] }, + { fields: ['active'] } + ] + }) + + return AuthOidcKey +} diff --git a/src/data/models/authOidcProviderState.js b/src/data/models/authOidcProviderState.js new file mode 100644 index 00000000..e05aae19 --- /dev/null +++ b/src/data/models/authOidcProviderState.js @@ -0,0 +1,72 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthOidcProviderState = sequelize.define('AuthOidcProviderState', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + model: { + type: DataTypes.STRING(64), + allowNull: false, + field: 'model' + }, + recordId: { + type: DataTypes.STRING(255), + allowNull: false, + field: 'record_id' + }, + payload: { + type: DataTypes.TEXT, + allowNull: false, + field: 'payload' + }, + expiresAt: { + type: DataTypes.DATE, + allowNull: true, + field: 'expires_at' + }, + grantId: { + type: DataTypes.STRING(255), + allowNull: true, + field: 'grant_id' + }, + uid: { + type: DataTypes.STRING(255), + allowNull: true, + field: 'uid' + }, + userCode: { + type: DataTypes.STRING(255), + allowNull: true, + field: 'user_code' + }, + consumed: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'consumed' + }, + consumedAt: { + type: DataTypes.DATE, + allowNull: true, + field: 'consumed_at' + } + }, { + tableName: 'AuthOidcProviderStates', + timestamps: true, + underscored: true, + indexes: [ + { unique: true, fields: ['model', 'record_id'] }, + { fields: ['grant_id'] }, + { fields: ['uid'] }, + { fields: ['user_code'] }, + { fields: ['expires_at'] } + ] + }) + + return AuthOidcProviderState +} diff --git a/src/data/models/authPasswordResetSession.js b/src/data/models/authPasswordResetSession.js new file mode 100644 index 00000000..22a1d8e8 --- /dev/null +++ b/src/data/models/authPasswordResetSession.js @@ -0,0 +1,44 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthPasswordResetSession = sequelize.define('AuthPasswordResetSession', { + id: { + type: DataTypes.STRING(36), + primaryKey: true, + allowNull: false, + field: 'id' + }, + userId: { + type: DataTypes.STRING(36), + allowNull: false, + field: 'user_id' + }, + expiresAt: { + type: DataTypes.DATE, + allowNull: false, + field: 'expires_at' + } + }, { + tableName: 'AuthPasswordResetSessions', + timestamps: true, + updatedAt: false, + underscored: true, + indexes: [ + { fields: ['user_id'] }, + { fields: ['expires_at'] } + ] + }) + + AuthPasswordResetSession.associate = function (models) { + AuthPasswordResetSession.belongsTo(models.AuthUser, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'user', + onDelete: 'CASCADE' + }) + } + + return AuthPasswordResetSession +} diff --git a/src/data/models/authPolicy.js b/src/data/models/authPolicy.js new file mode 100644 index 00000000..0c39ca87 --- /dev/null +++ b/src/data/models/authPolicy.js @@ -0,0 +1,90 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthPolicy = sequelize.define('AuthPolicy', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + allowNull: false, + defaultValue: 1, + field: 'id' + }, + minPasswordLength: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 12, + field: 'min_password_length' + }, + requireUppercase: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + field: 'require_uppercase' + }, + requireLowercase: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + field: 'require_lowercase' + }, + requireDigit: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + field: 'require_digit' + }, + passwordMaxAgeDays: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0, + field: 'password_max_age_days' + }, + passwordHistoryCount: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 5, + field: 'password_history_count' + }, + maxFailedAttempts: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 5, + field: 'max_failed_attempts' + }, + lockoutDurationMinutes: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 15, + field: 'lockout_duration_minutes' + }, + accessTokenTtlSeconds: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 900, + field: 'access_token_ttl_seconds' + }, + refreshTokenTtlSeconds: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 604800, + field: 'refresh_token_ttl_seconds' + }, + refreshRotation: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + field: 'refresh_rotation' + }, + maxConcurrentSessions: { + type: DataTypes.INTEGER, + allowNull: true, + field: 'max_concurrent_sessions' + } + }, { + tableName: 'AuthPolicy', + timestamps: true, + underscored: true + }) + + return AuthPolicy +} diff --git a/src/data/models/authRefreshToken.js b/src/data/models/authRefreshToken.js new file mode 100644 index 00000000..454387b1 --- /dev/null +++ b/src/data/models/authRefreshToken.js @@ -0,0 +1,62 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthRefreshToken = sequelize.define('AuthRefreshToken', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + tokenHash: { + type: DataTypes.STRING(255), + allowNull: false, + field: 'token_hash' + }, + userId: { + type: DataTypes.STRING(36), + allowNull: false, + field: 'user_id' + }, + familyId: { + type: DataTypes.STRING(36), + allowNull: false, + field: 'family_id' + }, + expiresAt: { + type: DataTypes.DATE, + allowNull: false, + field: 'expires_at' + }, + revoked: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'revoked' + } + }, { + tableName: 'AuthRefreshTokens', + timestamps: true, + underscored: true, + indexes: [ + { fields: ['token_hash'] }, + { fields: ['user_id'] }, + { fields: ['family_id'] }, + { fields: ['expires_at'] } + ] + }) + + AuthRefreshToken.associate = function (models) { + AuthRefreshToken.belongsTo(models.AuthUser, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'user', + onDelete: 'CASCADE' + }) + } + + return AuthRefreshToken +} diff --git a/src/data/models/authUser.js b/src/data/models/authUser.js new file mode 100644 index 00000000..6015cf20 --- /dev/null +++ b/src/data/models/authUser.js @@ -0,0 +1,105 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthUser = sequelize.define('AuthUser', { + id: { + type: DataTypes.STRING(36), + primaryKey: true, + allowNull: false, + field: 'id' + }, + email: { + type: DataTypes.STRING(255), + allowNull: false, + unique: true, + field: 'email' + }, + passwordHash: { + type: DataTypes.TEXT, + allowNull: false, + field: 'password_hash' + }, + passwordHistoryHashes: { + type: DataTypes.TEXT, + allowNull: true, + field: 'password_history_hashes' + }, + mustChangePassword: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'must_change_password' + }, + isBootstrap: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'is_bootstrap' + }, + failedAttempts: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0, + field: 'failed_attempts' + }, + lockedUntil: { + type: DataTypes.DATE, + allowNull: true, + field: 'locked_until' + }, + deletedAt: { + type: DataTypes.DATE, + allowNull: true, + field: 'deleted_at' + } + }, { + tableName: 'AuthUsers', + timestamps: true, + underscored: true, + indexes: [ + { unique: true, fields: ['email'] }, + { fields: ['deleted_at'] } + ] + }) + + AuthUser.associate = function (models) { + AuthUser.belongsToMany(models.AuthGroup, { + through: models.AuthUserGroup, + foreignKey: { + name: 'userId', + field: 'user_id' + }, + otherKey: { + name: 'groupId', + field: 'group_id' + }, + as: 'groups' + }) + AuthUser.hasOne(models.AuthMfa, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'mfa', + onDelete: 'CASCADE' + }) + AuthUser.hasMany(models.AuthRefreshToken, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'refreshTokens', + onDelete: 'CASCADE' + }) + AuthUser.hasMany(models.AuthPasswordResetSession, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'passwordResetSessions', + onDelete: 'CASCADE' + }) + } + + return AuthUser +} diff --git a/src/data/models/authUserGroup.js b/src/data/models/authUserGroup.js new file mode 100644 index 00000000..b6008958 --- /dev/null +++ b/src/data/models/authUserGroup.js @@ -0,0 +1,54 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthUserGroup = sequelize.define('AuthUserGroup', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + userId: { + type: DataTypes.STRING(36), + allowNull: false, + field: 'user_id' + }, + groupId: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'group_id' + } + }, { + tableName: 'AuthUserGroups', + timestamps: true, + updatedAt: false, + underscored: true, + indexes: [ + { unique: true, fields: ['user_id', 'group_id'] }, + { fields: ['user_id'] }, + { fields: ['group_id'] } + ] + }) + + AuthUserGroup.associate = function (models) { + AuthUserGroup.belongsTo(models.AuthUser, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'user', + onDelete: 'CASCADE' + }) + AuthUserGroup.belongsTo(models.AuthGroup, { + foreignKey: { + name: 'groupId', + field: 'group_id' + }, + as: 'group', + onDelete: 'CASCADE' + }) + } + + return AuthUserGroup +} diff --git a/src/data/providers/database-provider.js b/src/data/providers/database-provider.js index 3ae7d6bb..1f1cffc8 100644 --- a/src/data/providers/database-provider.js +++ b/src/data/providers/database-provider.js @@ -352,7 +352,6 @@ class DatabaseProvider { try { await db.query(query) } catch (err) { - // Check both the error and its parent (for Sequelize errors) const errorToCheck = err.parent || err if (errorToCheck.code === 'ER_TABLE_EXISTS_ERROR' || errorToCheck.code === 'ER_DUP_FIELDNAME' || @@ -413,10 +412,8 @@ class DatabaseProvider { try { await db.query(query) } catch (err) { - // Check both the error and its parent (for Sequelize errors) const errorToCheck = err.parent || err - // If transaction is aborted, rollback and start new transaction if (errorToCheck.code === '25P02') { logger.warn('Transaction aborted, rolling back and starting new transaction...') await db.query('ROLLBACK') @@ -424,16 +421,16 @@ class DatabaseProvider { continue } - if (errorToCheck.code === '42P07' || // duplicate_table - errorToCheck.code === '42701' || // duplicate_column - errorToCheck.code === '42P06' || // duplicate_schema - errorToCheck.code === '23505' || // unique_violation - errorToCheck.code === '23503' || // foreign_key_violation - errorToCheck.code === '42P01' || // undefined_table - errorToCheck.code === '42703' || // undefined_column - errorToCheck.code === '42P16' || // invalid_table_definition - errorToCheck.code === '42P17' || // invalid_table_definition - errorToCheck.code === '42P18' || // invalid_table_definition + if (errorToCheck.code === '42P07' || + errorToCheck.code === '42701' || + errorToCheck.code === '42P06' || + errorToCheck.code === '23505' || + errorToCheck.code === '23503' || + errorToCheck.code === '42P01' || + errorToCheck.code === '42703' || + errorToCheck.code === '42P16' || + errorToCheck.code === '42P17' || + errorToCheck.code === '42P18' || (errorToCheck.message && ( errorToCheck.message.includes('already exists') || errorToCheck.message.includes('duplicate key') || @@ -609,8 +606,8 @@ class DatabaseProvider { try { await db.query(query) } catch (err) { - if (err.code === '23505' || // unique_violation - err.code === '23503') { // foreign_key_violation + if (err.code === '23505' || + err.code === '23503') { logger.warn(`Ignored PostgreSQL error: ${err.message}`) } else { await db.query('ROLLBACK') diff --git a/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql b/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql index 5ff5df66..5a6e9fcc 100644 --- a/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql +++ b/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql @@ -48,4 +48,31 @@ VALUES (5, 3, 'ghcr.io/datasance/nats:latest'), (5, 4, 'ghcr.io/datasance/nats:latest'); +INSERT IGNORE INTO AuthPolicy ( + id, + min_password_length, + require_uppercase, + require_lowercase, + require_digit, + password_max_age_days, + password_history_count, + max_failed_attempts, + lockout_duration_minutes, + access_token_ttl_seconds, + refresh_token_ttl_seconds, + refresh_rotation, + max_concurrent_sessions +) +VALUES (1, 12, true, true, true, 0, 5, 5, 15, 900, 604800, true, NULL); + +INSERT IGNORE INTO AuthGroups (name, is_system) +VALUES + ('admin', true), + ('sre', true), + ('developer', true), + ('viewer', true); + +INSERT IGNORE INTO AuthBootstrapMeta (id, completed_at) +VALUES (1, NULL); + COMMIT; diff --git a/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql b/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql index eb6ec5af..976f4647 100644 --- a/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql +++ b/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql @@ -48,4 +48,34 @@ VALUES (5, 3, 'ghcr.io/datasance/nats:latest'), (5, 4, 'ghcr.io/datasance/nats:latest'); +INSERT INTO "AuthPolicy" ( + id, + min_password_length, + require_uppercase, + require_lowercase, + require_digit, + password_max_age_days, + password_history_count, + max_failed_attempts, + lockout_duration_minutes, + access_token_ttl_seconds, + refresh_token_ttl_seconds, + refresh_rotation, + max_concurrent_sessions +) +VALUES (1, 12, true, true, true, 0, 5, 5, 15, 900, 604800, true, NULL) +ON CONFLICT (id) DO NOTHING; + +INSERT INTO "AuthGroups" (name, is_system) +VALUES + ('admin', true), + ('sre', true), + ('developer', true), + ('viewer', true) +ON CONFLICT (name) DO NOTHING; + +INSERT INTO "AuthBootstrapMeta" (id, completed_at) +VALUES (1, NULL) +ON CONFLICT (id) DO NOTHING; + COMMIT; diff --git a/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql b/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql index 3cb80c52..5647391d 100644 --- a/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql +++ b/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql @@ -45,3 +45,30 @@ VALUES (5, 2, 'ghcr.io/datasance/nats:latest'), (5, 3, 'ghcr.io/datasance/nats:latest'), (5, 4, 'ghcr.io/datasance/nats:latest'); + +INSERT OR IGNORE INTO AuthPolicy ( + id, + min_password_length, + require_uppercase, + require_lowercase, + require_digit, + password_max_age_days, + password_history_count, + max_failed_attempts, + lockout_duration_minutes, + access_token_ttl_seconds, + refresh_token_ttl_seconds, + refresh_rotation, + max_concurrent_sessions +) +VALUES (1, 12, true, true, true, 0, 5, 5, 15, 900, 604800, true, NULL); + +INSERT OR IGNORE INTO AuthGroups (name, is_system) +VALUES + ('admin', true), + ('sre', true), + ('developer', true), + ('viewer', true); + +INSERT OR IGNORE INTO AuthBootstrapMeta (id, completed_at) +VALUES (1, NULL); From 4e0382cd07c0c3d8715861b14221f2fc5b5a0ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 05:41:01 +0300 Subject: [PATCH 42/75] Add embedded and external auth configuration with public URL and TLS env mapping. --- src/config/auth-jwks.js | 77 +++++++++ src/config/config.yaml | 28 ++-- src/config/embedded-oidc.js | 322 ++++++++++++++++++++++++++++++++++++ src/config/env-mapping.js | 22 ++- src/config/oidc.js | 133 ++++++++++++--- 5 files changed, 541 insertions(+), 41 deletions(-) create mode 100644 src/config/auth-jwks.js create mode 100644 src/config/embedded-oidc.js diff --git a/src/config/auth-jwks.js b/src/config/auth-jwks.js new file mode 100644 index 00000000..51b873be --- /dev/null +++ b/src/config/auth-jwks.js @@ -0,0 +1,77 @@ +'use strict' + +const { importJWK } = require('jose') +const db = require('../data/models') +const { withTransaction } = require('../helpers/app-helper') +const secretHelper = require('../helpers/secret-helper') + +let cachedSigningMaterial = null + +async function loadPrivateJwk (row) { + if (row.vaultRef) { + const data = await secretHelper.decryptSecret(row.vaultRef, `oidc-key-${row.kid}`, 'oidc-key') + return data.jwk + } + + if (row.keyMaterialEncrypted) { + const data = await secretHelper.decryptSecret(row.keyMaterialEncrypted, `oidc-key-${row.kid}`, 'oidc-key') + return data.jwk + } + + return null +} + +async function getActiveSigningMaterial (transaction, forceReload = false) { + if (cachedSigningMaterial && !forceReload) { + return cachedSigningMaterial + } + + if (!db.AuthOidcKey) { + throw new Error('Auth models are not initialized') + } + + const activeKeys = await db.AuthOidcKey.findAll(withTransaction(transaction, { + where: { active: true }, + order: [['id', 'ASC']] + })) + + for (const row of activeKeys) { + const privateJwk = await loadPrivateJwk(row) + if (privateJwk) { + cachedSigningMaterial = { + kid: privateJwk.kid || row.kid, + privateJwk, + signingKey: await importJWK(privateJwk, 'RS256') + } + return cachedSigningMaterial + } + } + + throw new Error('Embedded OIDC signing key is not configured') +} + +function getPublicJwk (privateJwk) { + const publicJwk = { ...privateJwk } + delete publicJwk.d + delete publicJwk.p + delete publicJwk.q + delete publicJwk.dp + delete publicJwk.dq + delete publicJwk.qi + return publicJwk +} + +function resetSigningMaterialCache () { + cachedSigningMaterial = null +} + +function resetSigningMaterialCacheForTests () { + resetSigningMaterialCache() +} + +module.exports = { + getActiveSigningMaterial, + getPublicJwk, + resetSigningMaterialCache, + resetSigningMaterialCacheForTests +} diff --git a/src/config/config.yaml b/src/config/config.yaml index 4476811e..f8043314 100644 --- a/src/config/config.yaml +++ b/src/config/config.yaml @@ -8,7 +8,9 @@ app: # Server Configuration server: port: 51121 # Server port number - devMode: true + devMode: true + publicUrl: "" # Canonical external URL (CONTROLLER_PUBLIC_URL); https required in prod unless auth.insecureAllowHttp + trustProxy: false # Honor X-Forwarded-* when behind reverse proxy (TRUST_PROXY) webSocket: perMessageDeflate: false allowExtensions: false # Disable all extensions @@ -25,15 +27,15 @@ server: maxConnectionsPerIp: 10 maxRequestsPerMinute: 60 maxPayload: 1048576 # 1MB - # ssl: + # tls: # path: - # key: "" # SSL key file path - # cert: "" # SSL certificate file path - # intermediateCert: "" # Intermediate certificate file path + # key: "" # TLS key file path (TLS_PATH_KEY) + # cert: "" # TLS certificate file path (TLS_PATH_CERT) + # intermediateCert: "" # Intermediate certificate file path (TLS_PATH_INTERMEDIATE_CERT) # base64: - # key: # SSL key in base64 format - # cert: # SSL certificate in base64 format - # intermediateCert: # Intermediate certificate in base64 format + # key: # TLS key in base64 format (TLS_BASE64_KEY) + # cert: # TLS certificate in base64 format (TLS_BASE64_CERT) + # intermediateCert: # Intermediate certificate in base64 format (TLS_BASE64_INTERMEDIATE_CERT) # Viewer Configuration viewer: @@ -93,9 +95,15 @@ database: min: 0 # Minimum connections idle: 20000 # Idle timeout in milliseconds -# Auth Configuration (OIDC — any compliant provider; env: OIDC_* per naming-map §13) +# Auth Configuration (OIDC — any compliant provider; env: OIDC_* / AUTH_* per naming-map §13) # auth: -# issuerUrl: # OIDC issuer URL (OIDC_ISSUER_URL); discovery resolves JWKS and endpoints +# mode: embedded # embedded | external (AUTH_MODE); resolution +# insecureAllowHttp: false # Allow http CONTROLLER_PUBLIC_URL in prod (AUTH_INSECURE_ALLOW_HTTP) +# bootstrap: +# adminEmail: "" # Bootstrap admin email (OIDC_BOOTSTRAP_ADMIN_EMAIL) +# adminPassword: "" # Bootstrap admin password (OIDC_BOOTSTRAP_ADMIN_PASSWORD) +# insecureAllowBootstrapLog: false # Allow bootstrap log in prod (AUTH_INSECURE_ALLOW_BOOTSTRAP_LOG) +# issuerUrl: # OIDC issuer URL (OIDC_ISSUER_URL); external mode only # client: # id: # Controller API client ID (OIDC_CLIENT_ID) # secret: # Controller API client secret (OIDC_CLIENT_SECRET) diff --git a/src/config/embedded-oidc.js b/src/config/embedded-oidc.js new file mode 100644 index 00000000..425dc80c --- /dev/null +++ b/src/config/embedded-oidc.js @@ -0,0 +1,322 @@ +'use strict' + +const crypto = require('crypto') +const { Provider } = require('oidc-provider') +const { generateKeyPair, exportJWK } = require('jose') +const config = require('./index') +const logger = require('../logger') +const secretHelper = require('../helpers/secret-helper') +const { getOidcSettings } = require('./oidc') +const { createOidcProviderAdapterFactory } = require('../data/adapters/oidc-provider-adapter') + +const DEFAULT_VIEWER_CLIENT_ID = 'ecn-viewer' +const CONTROLLER_CLIENT_ID = 'controller' + +let providerInstance = null + +function getPublicUrl () { + return (process.env.CONTROLLER_PUBLIC_URL || config.get('server.publicUrl') || '').replace(/\/$/, '') +} + +function isViewerClientEnabled () { + const envValue = process.env.AUTH_VIEWER_CLIENT_ENABLED + if (envValue !== undefined && envValue !== null && envValue !== '') { + return envValue === 'true' || envValue === '1' + } + return config.get('auth.viewerClient.enabled', false) === true +} + +function getViewerClientId () { + return process.env.OIDC_VIEWER_CLIENT_ID || + config.get('auth.viewerClient.id') || + config.get('auth.viewerClient') || + DEFAULT_VIEWER_CLIENT_ID +} + +function generateClientSecret () { + return crypto.randomBytes(32).toString('base64url') +} + +function getCookieKeys () { + const configured = process.env.OIDC_COOKIE_KEYS || config.get('auth.cookieKeys') + if (Array.isArray(configured)) { + return configured.filter(Boolean) + } + if (typeof configured === 'string' && configured.trim()) { + return configured.split(',').map((value) => value.trim()).filter(Boolean) + } + return ['controller-embedded-oidc-cookie-key'] +} + +function getTrustProxySetting () { + const trustProxy = process.env.TRUST_PROXY || config.get('server.trustProxy', false) + if (trustProxy === true || trustProxy === 'true' || trustProxy === 1 || trustProxy === '1') { + return true + } + return trustProxy || false +} + +async function resolveStoredSecret (secretRef, secretName, secretType) { + if (!secretRef) { + return null + } + + if (secretHelper.isVaultReference(secretRef)) { + const data = await secretHelper.decryptSecret(secretRef, secretName, secretType) + return data.secret || data.client_secret || data.value || null + } + + try { + const data = await secretHelper.decryptSecret(secretRef, secretName, secretType) + return data.secret || data.client_secret || data.value || null + } catch (error) { + return secretRef + } +} + +async function persistClientSecret (db, clientId, secret) { + const secretRef = await secretHelper.encryptSecret({ secret }, `oidc-client-${clientId}`, 'oidc-client') + const existing = await db.AuthOidcClient.findOne({ where: { clientId } }) + if (existing) { + await existing.update({ secretRef }) + return existing + } + + return db.AuthOidcClient.create({ + clientId, + secretRef, + clientType: 'confidential' + }) +} + +async function ensureConfidentialClientMetadata (db) { + const { clientId, clientSecret: envSecret } = getOidcSettings() + const resolvedClientId = clientId || CONTROLLER_CLIENT_ID + const publicUrl = getPublicUrl() + let clientRow = await db.AuthOidcClient.findOne({ where: { clientId: resolvedClientId } }) + + let secret = envSecret || null + if (!secret && clientRow && clientRow.secretRef) { + secret = await resolveStoredSecret(clientRow.secretRef, `oidc-client-${resolvedClientId}`, 'oidc-client') + } + + if (!secret) { + secret = generateClientSecret() + clientRow = await persistClientSecret(db, resolvedClientId, secret) + logger.info(`Generated embedded OIDC client secret for "${resolvedClientId}"`) + } else if (!clientRow) { + let secretRef = envSecret + if (!secretRef) { + secretRef = await secretHelper.encryptSecret({ secret }, `oidc-client-${resolvedClientId}`, 'oidc-client') + } + clientRow = await db.AuthOidcClient.create({ + clientId: resolvedClientId, + secretRef, + clientType: 'confidential' + }) + } + + return { + client_id: resolvedClientId, + client_secret: secret, + grant_types: ['authorization_code', 'refresh_token', 'client_credentials'], + response_types: ['code'], + token_endpoint_auth_method: 'client_secret_basic', + redirect_uris: [`${publicUrl}/api/v3/user/oauth/callback`] + } +} + +async function ensureViewerClientMetadata (db) { + if (!isViewerClientEnabled()) { + return null + } + + const clientId = getViewerClientId() + let clientRow = await db.AuthOidcClient.findOne({ where: { clientId } }) + if (!clientRow) { + clientRow = await db.AuthOidcClient.create({ + clientId, + clientType: 'public' + }) + } + + const publicUrl = getPublicUrl() + return { + client_id: clientId, + client_secret: undefined, + grant_types: ['authorization_code', 'refresh_token'], + response_types: ['code'], + token_endpoint_auth_method: 'none', + redirect_uris: [`${publicUrl}/`] + } +} + +async function loadPrivateJwk (row) { + if (row.vaultRef) { + const data = await secretHelper.decryptSecret(row.vaultRef, `oidc-key-${row.kid}`, 'oidc-key') + return data.jwk + } + + if (row.keyMaterialEncrypted) { + const data = await secretHelper.decryptSecret(row.keyMaterialEncrypted, `oidc-key-${row.kid}`, 'oidc-key') + return data.jwk + } + + return null +} + +async function ensureSigningJwks (db) { + const activeKeys = await db.AuthOidcKey.findAll({ + where: { active: true }, + order: [['id', 'ASC']] + }) + + const keys = [] + for (const row of activeKeys) { + const privateJwk = await loadPrivateJwk(row) + if (!privateJwk) { + continue + } + keys.push(privateJwk) + } + + if (keys.length > 0) { + return { keys } + } + + const { publicKey, privateKey } = await generateKeyPair('RS256') + const publicJwk = await exportJWK(publicKey) + const privateJwk = await exportJWK(privateKey) + const kid = crypto.randomUUID() + + publicJwk.kid = kid + publicJwk.alg = 'RS256' + publicJwk.use = 'sig' + privateJwk.kid = kid + privateJwk.alg = 'RS256' + privateJwk.use = 'sig' + + const keyMaterialEncrypted = await secretHelper.encryptSecret({ jwk: privateJwk }, `oidc-key-${kid}`, 'oidc-key') + await db.AuthOidcKey.create({ + kid, + keyMaterialEncrypted, + active: true + }) + + logger.info('Generated embedded OIDC signing key (JWKS)') + return { keys: [privateJwk] } +} + +async function loadTokenPolicy (db) { + const policy = await db.AuthPolicy.findByPk(1) + return { + accessTokenTtlSeconds: (policy && policy.accessTokenTtlSeconds) || 900, + refreshTokenTtlSeconds: (policy && policy.refreshTokenTtlSeconds) || 604800 + } +} + +async function buildProviderConfiguration (db) { + const clients = [await ensureConfidentialClientMetadata(db)] + const viewerClient = await ensureViewerClientMetadata(db) + if (viewerClient) { + clients.push(viewerClient) + } + + const tokenPolicy = await loadTokenPolicy(db) + const trustProxy = getTrustProxySetting() + + return { + adapter: createOidcProviderAdapterFactory(() => db.AuthOidcProviderState), + clients, + jwks: await ensureSigningJwks(db), + findAccount: async (ctx, id) => { + const user = await db.AuthUser.findByPk(id, { + include: [{ + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }] + }) + if (!user || user.deletedAt) { + return undefined + } + + const groupNames = (user.groups || []).map((group) => group.name) + + return { + accountId: id, + async claims () { + return { + sub: id, + email: user.email, + preferred_username: user.email, + groups: groupNames + } + } + } + }, + cookies: { + keys: getCookieKeys() + }, + features: { + devInteractions: { enabled: false }, + revocation: { enabled: true }, + registration: { enabled: false }, + deviceFlow: { enabled: false }, + introspection: { enabled: false } + }, + routes: { + revocation: '/revoke' + }, + scopes: ['openid', 'profile', 'email', 'groups'], + claims: { + openid: ['sub'], + email: ['email'], + profile: ['preferred_username'], + groups: ['groups'] + }, + pkce: { + required: (ctx, client) => client.tokenEndpointAuthMethod === 'none' + }, + proxy: trustProxy, + ttl: { + AccessToken: tokenPolicy.accessTokenTtlSeconds, + RefreshToken: tokenPolicy.refreshTokenTtlSeconds + } + } +} + +async function initEmbeddedIssuer (app, options = {}) { + if (providerInstance) { + return providerInstance + } + + const db = options.db || require('../data/models') + if (!db.AuthOidcKey || !db.AuthOidcClient || !db.AuthOidcProviderState) { + throw new Error('Embedded OIDC issuer requires auth models to be initialized') + } + + const issuer = getOidcSettings().issuerUrl + const configuration = await buildProviderConfiguration(db) + const provider = new Provider(issuer, configuration) + + app.use('/oidc', provider.callback()) + providerInstance = provider + logger.info(`Embedded OIDC issuer mounted at /oidc (issuer: ${issuer})`) + + return provider +} + +function getEmbeddedProvider () { + return providerInstance +} + +function resetEmbeddedIssuerForTests () { + providerInstance = null +} + +module.exports = { + initEmbeddedIssuer, + getEmbeddedProvider, + resetEmbeddedIssuerForTests +} diff --git a/src/config/env-mapping.js b/src/config/env-mapping.js index b44faa06..ae7f6055 100644 --- a/src/config/env-mapping.js +++ b/src/config/env-mapping.js @@ -8,6 +8,8 @@ module.exports = { // Server Configuration 'SERVER_PORT': 'server.port', 'SERVER_DEV_MODE': 'server.devMode', + 'CONTROLLER_PUBLIC_URL': 'server.publicUrl', + 'TRUST_PROXY': 'server.trustProxy', 'WS_PING_INTERVAL': 'server.webSocket.pingInterval', 'WS_PONG_TIMEOUT': 'server.webSocket.pongTimeout', @@ -19,13 +21,13 @@ module.exports = { 'WS_SECURITY_MAX_REQUESTS_PER_MINUTE': 'server.webSocket.security.maxRequestsPerMinute', 'WS_SECURITY_MAX_PAYLOAD': 'server.webSocket.security.maxPayload', - // SSL Configuration - 'SSL_PATH_KEY': 'server.ssl.path.key', - 'SSL_PATH_CERT': 'server.ssl.path.cert', - 'SSL_PATH_INTERMEDIATE_CERT': 'server.ssl.path.intermediateCert', - 'SSL_BASE64_KEY': 'server.ssl.base64.key', - 'SSL_BASE64_CERT': 'server.ssl.base64.cert', - 'SSL_BASE64_INTERMEDIATE_CERT': 'server.ssl.base64.intermediateCert', + // TLS Configuration (listener certificates; env TLS_* per Plan 8.1) + 'TLS_PATH_KEY': 'server.tls.path.key', + 'TLS_PATH_CERT': 'server.tls.path.cert', + 'TLS_PATH_INTERMEDIATE_CERT': 'server.tls.path.intermediateCert', + 'TLS_BASE64_KEY': 'server.tls.base64.key', + 'TLS_BASE64_CERT': 'server.tls.base64.cert', + 'TLS_BASE64_INTERMEDIATE_CERT': 'server.tls.base64.intermediateCert', // Viewer Configuration 'VIEWER_PORT': 'viewer.port', @@ -75,10 +77,16 @@ module.exports = { }, // Auth Configuration (OIDC — k8s-style; naming-map §13) + 'AUTH_MODE': 'auth.mode', 'OIDC_ISSUER_URL': 'auth.issuerUrl', 'OIDC_CLIENT_ID': 'auth.client.id', 'OIDC_CLIENT_SECRET': 'auth.client.secret', 'OIDC_VIEWER_CLIENT_ID': 'auth.viewerClient', + 'AUTH_VIEWER_CLIENT_ENABLED': 'auth.viewerClient.enabled', + 'AUTH_INSECURE_ALLOW_HTTP': 'auth.insecureAllowHttp', + 'OIDC_BOOTSTRAP_ADMIN_EMAIL': 'auth.bootstrap.adminEmail', + 'OIDC_BOOTSTRAP_ADMIN_PASSWORD': 'auth.bootstrap.adminPassword', + 'AUTH_INSECURE_ALLOW_BOOTSTRAP_LOG': 'auth.insecureAllowBootstrapLog', // Bridge Ports Configuration 'BRIDGE_PORTS_RANGE': 'bridgePorts.range', diff --git a/src/config/oidc.js b/src/config/oidc.js index 10fc6234..f66f66e1 100644 --- a/src/config/oidc.js +++ b/src/config/oidc.js @@ -22,9 +22,10 @@ */ const session = require('express-session') const oidcClient = require('openid-client') -const { createRemoteJWKSet, jwtVerify } = require('jose') +const { createRemoteJWKSet, createLocalJWKSet, jwtVerify } = require('jose') const config = require('./index') const logger = require('../logger') +const { getActiveSigningMaterial, getPublicJwk } = require('./auth-jwks') let oidcInstance = null let memoryStore = null @@ -32,9 +33,26 @@ let discoveryPromise = null let jwks = null let issuerString = null let configuredClientId = null -let devMode = false -function getOidcSettings () { +const AUTH_MODES = ['embedded', 'external'] + +function isNonEmptyString (value) { + return value !== undefined && value !== null && value !== '' +} + +function getAuthMode () { + const mode = process.env.AUTH_MODE || config.get('auth.mode') || 'embedded' + if (!AUTH_MODES.includes(mode)) { + throw new Error(`Invalid auth.mode "${mode}". Must be embedded or external`) + } + return mode +} + +function getPublicUrl () { + return process.env.CONTROLLER_PUBLIC_URL || config.get('server.publicUrl') || '' +} + +function getExternalOidcSettings () { return { issuerUrl: process.env.OIDC_ISSUER_URL || config.get('auth.issuerUrl') || config.get('auth.url'), clientId: process.env.OIDC_CLIENT_ID || config.get('auth.client.id'), @@ -42,11 +60,53 @@ function getOidcSettings () { } } +function getEmbeddedIssuerUrl () { + const publicUrl = getPublicUrl().replace(/\/$/, '') + return `${publicUrl}/oidc` +} + +function getOidcSettings () { + const mode = getAuthMode() + if (mode === 'embedded') { + return { + issuerUrl: getEmbeddedIssuerUrl(), + clientId: process.env.OIDC_CLIENT_ID || config.get('auth.client.id') || 'controller', + clientSecret: process.env.OIDC_CLIENT_SECRET || config.get('auth.client.secret') || '' + } + } + return getExternalOidcSettings() +} + +function isEmbeddedAuthConfigured () { + return isNonEmptyString(getPublicUrl()) +} + +function isExternalAuthConfigured () { + const { issuerUrl, clientId, clientSecret } = getExternalOidcSettings() + return [issuerUrl, clientId, clientSecret].every(isNonEmptyString) +} + function isAuthConfigured () { - const { issuerUrl, clientId, clientSecret } = getOidcSettings() - return [issuerUrl, clientId, clientSecret].every( - value => value !== undefined && value !== null && value !== '' - ) + const mode = getAuthMode() + return mode === 'embedded' ? isEmbeddedAuthConfigured() : isExternalAuthConfigured() +} + +function validateAuthConfig () { + const mode = getAuthMode() + if (mode === 'embedded') { + if (!isEmbeddedAuthConfigured()) { + throw new Error('Embedded auth requires CONTROLLER_PUBLIC_URL (server.publicUrl)') + } + const externalIssuer = process.env.OIDC_ISSUER_URL || config.get('auth.issuerUrl') + if (isNonEmptyString(externalIssuer)) { + logger.warn('auth.issuerUrl is set but auth.mode is embedded; embedded issuer will be used') + } + return + } + + if (!isExternalAuthConfigured()) { + throw new Error('External auth requires OIDC_ISSUER_URL, OIDC_CLIENT_ID, and OIDC_CLIENT_SECRET') + } } /** @@ -63,11 +123,27 @@ function buildKauthGrant (claims, rawToken) { } } +async function ensureEmbeddedJwks () { + const { privateJwk } = await getActiveSigningMaterial() + jwks = createLocalJWKSet({ keys: [getPublicJwk(privateJwk)] }) + issuerString = getEmbeddedIssuerUrl() + configuredClientId = getOidcSettings().clientId + return null +} + async function ensureDiscovery () { if (discoveryPromise) { return discoveryPromise } + if (getAuthMode() === 'embedded') { + discoveryPromise = ensureEmbeddedJwks().catch((error) => { + discoveryPromise = null + throw error + }) + return discoveryPromise + } + const { issuerUrl, clientId, clientSecret } = getOidcSettings() configuredClientId = clientId @@ -109,26 +185,22 @@ function initOidc () { } // v3.9: read TLS client-auth policy when HTTPS + requestCert enabled (server.js listener) - const isDevMode = config.get('server.devMode', true) - const hasAuthConfig = isAuthConfigured() - devMode = isDevMode && !hasAuthConfig - - if (devMode) { + if (!isAuthConfigured()) { + const isProduction = !config.get('server.devMode', true) + if (isProduction) { + const error = new Error('Auth configuration required in production mode') + logger.error('Failed to initialize OIDC:', error) + throw error + } + logger.warn('OIDC not configured; bearer validation unavailable until auth is configured') oidcInstance = createOidcFacade() - logger.warn('OIDC initialized in development mode (no auth configuration)') - logger.warn('WARNING: All routes are unprotected in this mode') return oidcInstance } - if (!hasAuthConfig) { - const error = new Error('Auth configuration required in production mode') - logger.error('Failed to initialize OIDC:', error) - throw error - } - + validateAuthConfig() memoryStore = new session.MemoryStore() oidcInstance = createOidcFacade() - logger.info('OIDC initialized successfully with auth configuration') + logger.info(`OIDC initialized successfully (${getAuthMode()} mode)`) return oidcInstance } @@ -138,13 +210,13 @@ function getOidc () { function getOidcMiddleware () { return async (req, res, next) => { - if (devMode) { + if (!isAuthConfigured()) { return next() } // Agent routes use fog JWTs (checkFogToken), not OIDC bearer tokens const requestPath = req.path || (req.url && req.url.split('?')[0]) || '' - if (requestPath.startsWith('/api/v3/agent')) { + if (requestPath.startsWith('/api/v3/agent') || requestPath.startsWith('/oidc')) { return next() } @@ -187,12 +259,25 @@ async function getOidcConfiguration () { return ensureDiscovery() } +function resetDiscoveryForTests () { + discoveryPromise = null + jwks = null + issuerString = null + configuredClientId = null + oidcInstance = null + memoryStore = null +} + module.exports = { initOidc, getOidc, getOidcMiddleware, getMemoryStore, + getAuthMode, isAuthConfigured, + validateAuthConfig, getOidcSettings, - getOidcConfiguration + getOidcConfiguration, + resetDiscoveryForTests, + buildKauthGrant } From 6afd46bf964a4206ae81c97faa67f1a1e7a600d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 05:41:07 +0300 Subject: [PATCH 43/75] Add embedded auth services for login, MFA, tokens, users, and OAuth BFF. --- src/helpers/app-helper.js | 8 + src/helpers/constants.js | 1 + src/helpers/errors.js | 11 +- src/services/auth-bootstrap-service.js | 158 ++++++++ src/services/auth-jwks-service.js | 55 +++ src/services/auth-login-service.js | 85 +++++ src/services/auth-mfa-service.js | 224 ++++++++++++ src/services/auth-migration-service.js | 43 +++ src/services/auth-oauth-service.js | 167 +++++++++ src/services/auth-password-service.js | 136 +++++++ src/services/auth-policy-service.js | 69 ++++ src/services/auth-token-service.js | 143 ++++++++ src/services/auth-user-service.js | 480 +++++++++++++++++++++++++ 13 files changed, 1579 insertions(+), 1 deletion(-) create mode 100644 src/services/auth-bootstrap-service.js create mode 100644 src/services/auth-jwks-service.js create mode 100644 src/services/auth-login-service.js create mode 100644 src/services/auth-mfa-service.js create mode 100644 src/services/auth-migration-service.js create mode 100644 src/services/auth-oauth-service.js create mode 100644 src/services/auth-password-service.js create mode 100644 src/services/auth-policy-service.js create mode 100644 src/services/auth-token-service.js create mode 100644 src/services/auth-user-service.js diff --git a/src/helpers/app-helper.js b/src/helpers/app-helper.js index e3377a9a..472b7282 100644 --- a/src/helpers/app-helper.js +++ b/src/helpers/app-helper.js @@ -131,6 +131,13 @@ function checkTransaction (transaction) { } } +function withTransaction (transaction, options = {}) { + if (transaction && !transaction.fakeTransaction) { + options.transaction = transaction + } + return options +} + function deleteUndefinedFields (obj) { if (!obj) { return @@ -206,6 +213,7 @@ module.exports = { checkPortAvailability, generateAccessToken, checkTransaction, + withTransaction, deleteUndefinedFields, validateBooleanCliOptions, formatMessage, diff --git a/src/helpers/constants.js b/src/helpers/constants.js index 939afcfe..0725a040 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -61,6 +61,7 @@ module.exports = { HTTP_CODE_UNAUTHORIZED: 401, HTTP_CODE_FORBIDDEN: 403, HTTP_CODE_NOT_FOUND: 404, + HTTP_CODE_NOT_IMPLEMENTED: 501, HTTP_CODE_DUPLICATE_PROPERTY: 409, HTTP_CODE_INTERNAL_ERROR: 500, diff --git a/src/helpers/errors.js b/src/helpers/errors.js index 95aab9a9..aef56300 100644 --- a/src/helpers/errors.js +++ b/src/helpers/errors.js @@ -118,6 +118,14 @@ class ForbiddenError extends Error { } } +class NotImplementedError extends Error { + constructor (message = 'This endpoint is only available in embedded auth mode') { + super(message) + this.message = message + this.name = 'NotImplementedError' + } +} + module.exports = { AuthenticationError: AuthenticationError, TransactionError: TransactionError, @@ -131,5 +139,6 @@ module.exports = { InvalidArgumentTypeError: InvalidArgumentTypeError, CLIArgsNotProvidedError: CLIArgsNotProvidedError, ConflictError: ConflictError, - ForbiddenError: ForbiddenError + ForbiddenError: ForbiddenError, + NotImplementedError: NotImplementedError } diff --git a/src/services/auth-bootstrap-service.js b/src/services/auth-bootstrap-service.js new file mode 100644 index 00000000..6c5bb754 --- /dev/null +++ b/src/services/auth-bootstrap-service.js @@ -0,0 +1,158 @@ +'use strict' + +const crypto = require('crypto') +const config = require('../config') +const logger = require('../logger') +const db = require('../data/models') +const secretHelper = require('../helpers/secret-helper') +const AuthPasswordService = require('./auth-password-service') +const AuthPolicyService = require('./auth-policy-service') +const { ADMIN_GROUP } = require('./auth-mfa-service') + +const SYSTEM_GROUPS = ['admin', 'sre', 'developer', 'viewer'] + +async function resolveBootstrapPassword (passwordRef) { + if (!passwordRef) { + return null + } + + if (secretHelper.isVaultReference(passwordRef)) { + const data = await secretHelper.decryptSecret(passwordRef, 'bootstrap-admin-password', 'auth-bootstrap') + return data.password || data.secret || data.value || null + } + + try { + const data = await secretHelper.decryptSecret(passwordRef, 'bootstrap-admin-password', 'auth-bootstrap') + return data.password || data.secret || data.value || passwordRef + } catch (error) { + return passwordRef + } +} + +function getBootstrapConfig () { + return { + email: (process.env.OIDC_BOOTSTRAP_ADMIN_EMAIL || config.get('auth.bootstrap.adminEmail') || '').trim().toLowerCase(), + passwordRef: process.env.OIDC_BOOTSTRAP_ADMIN_PASSWORD || config.get('auth.bootstrap.adminPassword') || '', + allowBootstrapLog: config.get('auth.insecureAllowBootstrapLog', false) === true + } +} + +async function ensureSystemGroups (transaction) { + for (const name of SYSTEM_GROUPS) { + await db.AuthGroup.findOrCreate({ + where: { name }, + defaults: { name, isSystem: true }, + transaction + }) + } +} + +async function runBootstrap (outerTransaction) { + const transaction = outerTransaction || await db.sequelize.transaction() + const ownTransaction = !outerTransaction + + try { + await ensureSystemGroups(transaction) + + let meta = await db.AuthBootstrapMeta.findByPk(1, { + transaction, + lock: transaction.LOCK.UPDATE + }) + if (!meta) { + meta = await db.AuthBootstrapMeta.create({ id: 1 }, { transaction }) + } + + if (meta.completedAt) { + if (ownTransaction) { + await transaction.commit() + } + return { skipped: true, reason: 'already_completed' } + } + + const { email, passwordRef, allowBootstrapLog } = getBootstrapConfig() + if (!email || !passwordRef) { + logger.warn('Embedded auth bootstrap skipped: OIDC_BOOTSTRAP_ADMIN_EMAIL and OIDC_BOOTSTRAP_ADMIN_PASSWORD are required for first boot') + if (ownTransaction) { + await transaction.commit() + } + return { skipped: true, reason: 'missing_credentials' } + } + + const plainPassword = await resolveBootstrapPassword(passwordRef) + if (!plainPassword) { + logger.warn('Embedded auth bootstrap skipped: bootstrap admin password could not be resolved') + if (ownTransaction) { + await transaction.commit() + } + return { skipped: true, reason: 'missing_credentials' } + } + + const policy = await AuthPolicyService.getPolicy(transaction) + AuthPasswordService.validatePasswordComplexity(plainPassword, policy) + + const existingUser = await db.AuthUser.findOne({ + where: { email, deletedAt: null }, + transaction + }) + if (existingUser) { + await meta.update({ + completedAt: new Date(), + bootstrapAdminUserId: existingUser.id + }, { transaction }) + logger.info(`Embedded auth bootstrap marked complete for existing user ${email}`) + if (ownTransaction) { + await transaction.commit() + } + return { skipped: true, reason: 'user_exists', userId: existingUser.id } + } + + const adminGroup = await db.AuthGroup.findOne({ + where: { name: ADMIN_GROUP }, + transaction + }) + if (!adminGroup) { + throw new Error('System admin group is missing from AuthGroups') + } + + const userId = crypto.randomUUID() + const passwordHash = await AuthPasswordService.hashPassword(plainPassword) + const user = await db.AuthUser.create({ + id: userId, + email, + passwordHash, + isBootstrap: true, + mustChangePassword: false + }, { transaction }) + + await db.AuthUserGroup.create({ + userId: user.id, + groupId: adminGroup.id + }, { transaction }) + + await meta.update({ + completedAt: new Date(), + bootstrapAdminUserId: user.id + }, { transaction }) + + logger.info(`Embedded auth bootstrap admin created for ${email}`) + if (allowBootstrapLog) { + logger.warn(`Bootstrap admin temporary password for ${email}: ${plainPassword}`) + } + + if (ownTransaction) { + await transaction.commit() + } + return { skipped: false, userId: user.id, email } + } catch (error) { + if (ownTransaction) { + await transaction.rollback() + } + throw error + } +} + +module.exports = { + SYSTEM_GROUPS, + ensureSystemGroups, + runBootstrap +} diff --git a/src/services/auth-jwks-service.js b/src/services/auth-jwks-service.js new file mode 100644 index 00000000..3f21e6be --- /dev/null +++ b/src/services/auth-jwks-service.js @@ -0,0 +1,55 @@ +'use strict' + +const crypto = require('crypto') +const { generateKeyPair, exportJWK } = require('jose') +const db = require('../data/models') +const secretHelper = require('../helpers/secret-helper') +const { withTransaction } = require('../helpers/app-helper') +const TransactionDecorator = require('../decorators/transaction-decorator') +const { resetSigningMaterialCache } = require('../config/auth-jwks') +const AuthUserService = require('./auth-user-service') + +async function rotateSigningKey (transaction) { + AuthUserService.ensureEmbeddedMode() + + await db.AuthOidcKey.update( + { active: false }, + withTransaction(transaction, { where: { active: true } }) + ) + + const { publicKey, privateKey } = await generateKeyPair('RS256') + const publicJwk = await exportJWK(publicKey) + const privateJwk = await exportJWK(privateKey) + const kid = crypto.randomUUID() + + publicJwk.kid = kid + publicJwk.alg = 'RS256' + publicJwk.use = 'sig' + privateJwk.kid = kid + privateJwk.alg = 'RS256' + privateJwk.use = 'sig' + + const keyMaterialEncrypted = await secretHelper.encryptSecret( + { jwk: privateJwk }, + `oidc-key-${kid}`, + 'oidc-key' + ) + + await db.AuthOidcKey.create({ + kid, + keyMaterialEncrypted, + active: true + }, withTransaction(transaction)) + + resetSigningMaterialCache() + + return { + kid, + rotatedAt: new Date().toISOString(), + restartRequired: true + } +} + +module.exports = { + rotateSigningKey: TransactionDecorator.generateTransaction(rotateSigningKey) +} diff --git a/src/services/auth-login-service.js b/src/services/auth-login-service.js new file mode 100644 index 00000000..5777ed43 --- /dev/null +++ b/src/services/auth-login-service.js @@ -0,0 +1,85 @@ +'use strict' + +const { decodeJwt } = require('jose') +const Errors = require('../helpers/errors') +const AuthPolicyService = require('./auth-policy-service') +const AuthPasswordService = require('./auth-password-service') +const AuthTokenService = require('./auth-token-service') +const AuthMfaService = require('./auth-mfa-service') + +async function completeLogin (authContext, transaction) { + const { user, groupNames } = authContext + await AuthPolicyService.resetFailedLogin(user, transaction) + return AuthTokenService.issueTokenPair(user, groupNames, transaction) +} + +async function login (credentials, transaction) { + const authContext = await AuthMfaService.loadUserAuthContext(credentials.email, transaction) + if (!authContext) { + throw new Errors.InvalidCredentialsError() + } + + const { user, groups, mfa } = authContext + const policy = await AuthPolicyService.getPolicy(transaction) + + if (AuthPolicyService.isAccountLocked(user, policy)) { + throw new Errors.InvalidCredentialsError() + } + + if (!await AuthPasswordService.verifyPassword(credentials.password, user.passwordHash)) { + await AuthPolicyService.recordFailedLogin(user, policy, transaction) + throw new Errors.InvalidCredentialsError() + } + + if (AuthMfaService.userMustEnrollMfa(user, groups, mfa)) { + throw new Errors.InvalidCredentialsError() + } + + if (AuthMfaService.userRequiresMfaChallenge(user, groups, mfa)) { + const totp = credentials.totp != null ? String(credentials.totp).trim() : '' + if (!totp) { + throw new Errors.InvalidCredentialsError() + } + await AuthMfaService.verifyMfaCode(user.id, totp, transaction) + } + + return completeLogin(authContext, transaction) +} + +async function refresh ({ refreshToken }, transaction) { + return AuthTokenService.rotateRefreshToken(refreshToken, transaction) +} + +async function profile (req, transaction) { + const accessToken = req.headers.authorization.replace('Bearer ', '') + const claims = decodeJwt(accessToken) + + return { + sub: claims.sub, + email: claims.email, + preferred_username: claims.preferred_username, + groups: claims.groups || [] + } +} + +async function logout (req, transaction) { + const accessToken = req.headers.authorization.replace('Bearer ', '') + + try { + const claims = decodeJwt(accessToken) + if (claims.sub) { + await AuthTokenService.revokeAllUserRefreshTokens(claims.sub, transaction) + } + } catch (error) { + // Best-effort logout + } + + return { status: 'success' } +} + +module.exports = { + login, + refresh, + profile, + logout +} diff --git a/src/services/auth-mfa-service.js b/src/services/auth-mfa-service.js new file mode 100644 index 00000000..81a55b0d --- /dev/null +++ b/src/services/auth-mfa-service.js @@ -0,0 +1,224 @@ +'use strict' + +const { generateSecret, verify, generateURI } = require('otplib') +const db = require('../data/models') +const Errors = require('../helpers/errors') +const { withTransaction } = require('../helpers/app-helper') +const secretHelper = require('../helpers/secret-helper') +const { + verifyPassword, + generateRecoveryCodes, + hashRecoveryCodes, + verifyRecoveryCode, + consumeRecoveryCode +} = require('./auth-password-service') + +const ADMIN_GROUP = 'admin' + +function normalizeGroupNames (groups) { + return (groups || []).map((group) => String(group.name || group).toLowerCase()) +} + +function isMfaExempt (user) { + return Boolean(user && user.isBootstrap) +} + +function userRequiresMfa (groups) { + return normalizeGroupNames(groups).includes(ADMIN_GROUP) +} + +function userMustEnrollMfa (user, groups, mfaRecord) { + if (isMfaExempt(user)) { + return false + } + return userRequiresMfa(groups) && (!mfaRecord || !mfaRecord.enabled) +} + +function userRequiresMfaChallenge (user, groups, mfaRecord) { + if (isMfaExempt(user)) { + return false + } + return userRequiresMfa(groups) && Boolean(mfaRecord && mfaRecord.enabled) +} + +async function loadUserAuthContext (email, transaction) { + const normalizedEmail = String(email || '').trim().toLowerCase() + const user = await db.AuthUser.findOne(withTransaction(transaction, { + where: { + email: normalizedEmail, + deletedAt: null + }, + include: [ + { + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }, + { + model: db.AuthMfa, + as: 'mfa' + } + ] + })) + + if (!user) { + return null + } + + return { + user, + groups: user.groups || [], + mfa: user.mfa || null, + groupNames: normalizeGroupNames(user.groups) + } +} + +async function decryptTotpSecret (mfaRecord) { + if (!mfaRecord || !mfaRecord.totpSecretEncrypted) { + return null + } + + const data = await secretHelper.decryptSecret( + mfaRecord.totpSecretEncrypted, + `auth-mfa-${mfaRecord.userId}`, + 'auth-mfa' + ) + return data.secret || data.totpSecret || null +} + +async function verifyTotpCode (mfaRecord, code) { + const secret = await decryptTotpSecret(mfaRecord) + if (!secret) { + return false + } + const result = await verify({ token: code, secret }) + return result.valid === true +} + +async function verifyMfaCode (userId, code, transaction) { + const mfaRecord = await db.AuthMfa.findOne(withTransaction(transaction, { + where: { userId } + })) + + if (!mfaRecord || !mfaRecord.enabled) { + throw new Errors.InvalidCredentialsError() + } + + if (await verifyTotpCode(mfaRecord, code)) { + return true + } + + const updatedRecoveryHashes = await consumeRecoveryCode(code, mfaRecord.recoveryCodesHash) + if (updatedRecoveryHashes) { + await mfaRecord.update({ recoveryCodesHash: updatedRecoveryHashes }, withTransaction(transaction)) + return true + } + + throw new Errors.InvalidCredentialsError() +} + +async function enrollMfa (userId, transaction) { + const existing = await db.AuthMfa.findOne(withTransaction(transaction, { where: { userId } })) + if (existing && existing.enabled) { + throw new Errors.ValidationError('MFA is already enabled') + } + + const user = await db.AuthUser.findByPk(userId, withTransaction(transaction)) + if (!user || user.deletedAt) { + throw new Errors.NotFoundError('User not found') + } + + const secret = generateSecret() + const totpSecretEncrypted = await secretHelper.encryptSecret( + { secret }, + `auth-mfa-${userId}`, + 'auth-mfa' + ) + + if (existing) { + await existing.update({ + totpSecretEncrypted, + enabled: false, + recoveryCodesHash: null + }, withTransaction(transaction)) + } else { + await db.AuthMfa.create({ + userId, + totpSecretEncrypted, + enabled: false + }, withTransaction(transaction)) + } + + const otpauthUrl = generateURI({ + issuer: 'Controller', + label: user.email, + secret + }) + return { + secret, + otpauthUrl + } +} + +async function confirmMfa (userId, code, transaction) { + const mfaRecord = await db.AuthMfa.findOne(withTransaction(transaction, { where: { userId } })) + if (!mfaRecord || !mfaRecord.totpSecretEncrypted) { + throw new Errors.ValidationError('MFA enrollment has not been started') + } + if (mfaRecord.enabled) { + throw new Errors.ValidationError('MFA is already enabled') + } + + if (!await verifyTotpCode(mfaRecord, code)) { + throw new Errors.InvalidCredentialsError() + } + + const recoveryCodes = generateRecoveryCodes() + const recoveryCodesHash = await hashRecoveryCodes(recoveryCodes) + await mfaRecord.update({ + enabled: true, + recoveryCodesHash + }, withTransaction(transaction)) + + return { recoveryCodes } +} + +async function disableMfa (userId, password, code, transaction) { + const user = await db.AuthUser.findByPk(userId, withTransaction(transaction, { + include: [{ model: db.AuthMfa, as: 'mfa' }] + })) + + if (!user || user.deletedAt) { + throw new Errors.NotFoundError('User not found') + } + if (!user.mfa || !user.mfa.enabled) { + throw new Errors.ValidationError('MFA is not enabled') + } + if (!await verifyPassword(password, user.passwordHash)) { + throw new Errors.InvalidCredentialsError() + } + if (!await verifyTotpCode(user.mfa, code) && !await verifyRecoveryCode(code, user.mfa.recoveryCodesHash)) { + throw new Errors.InvalidCredentialsError() + } + + await user.mfa.update({ + enabled: false, + totpSecretEncrypted: null, + recoveryCodesHash: null + }, withTransaction(transaction)) + + return { status: 'success' } +} + +module.exports = { + ADMIN_GROUP, + isMfaExempt, + userRequiresMfa, + userMustEnrollMfa, + userRequiresMfaChallenge, + loadUserAuthContext, + verifyMfaCode, + enrollMfa, + confirmMfa, + disableMfa +} diff --git a/src/services/auth-migration-service.js b/src/services/auth-migration-service.js new file mode 100644 index 00000000..101f7306 --- /dev/null +++ b/src/services/auth-migration-service.js @@ -0,0 +1,43 @@ +'use strict' + +const db = require('../data/models') +const { withTransaction } = require('../helpers/app-helper') +const TransactionDecorator = require('../decorators/transaction-decorator') +const AuthUserService = require('./auth-user-service') + +async function exportMigrationData (transaction) { + AuthUserService.ensureEmbeddedMode() + + const users = await db.AuthUser.findAll(withTransaction(transaction, { + where: { deletedAt: null }, + include: [{ + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }], + order: [['email', 'ASC']] + })) + + const groups = await db.AuthGroup.findAll(withTransaction(transaction, { + order: [['name', 'ASC']] + })) + + return { + exportedAt: new Date().toISOString(), + authMode: 'embedded', + users: users.map((user) => ({ + id: user.id, + email: user.email, + groups: (user.groups || []).map((group) => group.name) + })), + groups: groups.map((group) => ({ + id: group.id, + name: group.name, + isSystem: group.isSystem + })) + } +} + +module.exports = { + exportMigrationData: TransactionDecorator.generateTransaction(exportMigrationData) +} diff --git a/src/services/auth-oauth-service.js b/src/services/auth-oauth-service.js new file mode 100644 index 00000000..750a88c6 --- /dev/null +++ b/src/services/auth-oauth-service.js @@ -0,0 +1,167 @@ +'use strict' + +const { decodeJwt } = require('jose') +const { + buildAuthorizationUrl, + authorizationCodeGrant, + randomState, + randomNonce +} = require('openid-client') +const config = require('../config') +const Errors = require('../helpers/errors') +const logger = require('../logger') +const { + getOidcConfiguration, + getAuthMode, + isAuthConfigured +} = require('../config/oidc') + +const OAUTH_SESSION_KEY = 'controllerOauth' +const OAUTH_SESSION_TTL_MS = 10 * 60 * 1000 + +function ensureAuthConfigured () { + if (!isAuthConfigured()) { + throw new Error('Auth is not configured for this cluster. Please contact your administrator.') + } +} + +function ensureExternalMode () { + if (getAuthMode() !== 'external') { + throw new Errors.NotImplementedError('OAuth BFF is only available in external auth mode') + } +} + +function getPublicUrl () { + return (process.env.CONTROLLER_PUBLIC_URL || config.get('server.publicUrl') || '').replace(/\/$/, '') +} + +function getRedirectUri () { + return `${getPublicUrl()}/api/v3/user/oauth/callback` +} + +function getViewerUrl () { + const viewerUrl = process.env.VIEWER_URL || config.get('viewer.url') + if (!viewerUrl) { + return null + } + return String(viewerUrl).replace(/\/$/, '') +} + +function ensureOauthSession (sessionData) { + if (!sessionData || !sessionData.state || !sessionData.nonce) { + throw new Errors.AuthenticationError('OAuth session expired or missing') + } + + if (Date.now() - sessionData.createdAt > OAUTH_SESSION_TTL_MS) { + throw new Errors.AuthenticationError('OAuth session expired') + } +} + +function extractEmailFromTokenResponse (tokenResponse) { + if (tokenResponse.id_token) { + try { + const claims = decodeJwt(tokenResponse.id_token) + if (claims.email) { + return String(claims.email).trim().toLowerCase() + } + if (claims.preferred_username && claims.preferred_username.includes('@')) { + return String(claims.preferred_username).trim().toLowerCase() + } + } catch (error) { + logger.warn({ msg: 'Failed to decode OAuth ID token for email linking', err: error.message }) + } + } + + if (tokenResponse.access_token) { + try { + const claims = decodeJwt(tokenResponse.access_token) + if (claims.email) { + return String(claims.email).trim().toLowerCase() + } + if (claims.preferred_username && claims.preferred_username.includes('@')) { + return String(claims.preferred_username).trim().toLowerCase() + } + } catch (error) { + logger.warn({ msg: 'Failed to decode OAuth access token for email linking', err: error.message }) + } + } + + return null +} + +/** + * External mode links identities by email claim only — RBAC User subjects use + * preferred_username/email from JWT. No AuthUsers row is created in external mode. + */ +function linkExternalUserByEmail (tokenResponse) { + const email = extractEmailFromTokenResponse(tokenResponse) + if (email) { + logger.info(`External OAuth login linked by email: ${email}`) + } + return email +} + +async function authorize (req) { + ensureAuthConfigured() + ensureExternalMode() + + const oidcConfig = await getOidcConfiguration() + const state = randomState() + const nonce = randomNonce() + + req.session[OAUTH_SESSION_KEY] = { + state, + nonce, + createdAt: Date.now() + } + + const authorizationUrl = buildAuthorizationUrl(oidcConfig, { + redirect_uri: getRedirectUri(), + scope: 'openid profile email', + state, + nonce + }) + + return { redirectUrl: authorizationUrl.toString() } +} + +async function callback (req) { + ensureAuthConfigured() + ensureExternalMode() + + const sessionData = req.session[OAUTH_SESSION_KEY] + ensureOauthSession(sessionData) + delete req.session[OAUTH_SESSION_KEY] + + const oidcConfig = await getOidcConfiguration() + const currentUrl = new URL(`${getPublicUrl()}${req.originalUrl}`) + + let tokenResponse + try { + tokenResponse = await authorizationCodeGrant(oidcConfig, currentUrl, { + expectedState: sessionData.state, + expectedNonce: sessionData.nonce + }) + } catch (error) { + throw new Errors.AuthenticationError(error.message || 'OAuth authorization failed') + } + + linkExternalUserByEmail(tokenResponse) + + const tokens = { + accessToken: tokenResponse.access_token, + refreshToken: tokenResponse.refresh_token || null + } + + return { + tokens, + viewerUrl: getViewerUrl() + } +} + +module.exports = { + authorize, + callback, + getRedirectUri, + getViewerUrl +} diff --git a/src/services/auth-password-service.js b/src/services/auth-password-service.js new file mode 100644 index 00000000..b325ddeb --- /dev/null +++ b/src/services/auth-password-service.js @@ -0,0 +1,136 @@ +'use strict' + +const crypto = require('crypto') +const { hash, verify } = require('@node-rs/argon2') +const Errors = require('../helpers/errors') + +const ARGON2_OPTIONS = { + memoryCost: 19456, + timeCost: 2, + parallelism: 1 +} + +async function hashPassword (plainPassword) { + return hash(plainPassword, ARGON2_OPTIONS) +} + +async function verifyPassword (plainPassword, passwordHash) { + if (!plainPassword || !passwordHash) { + return false + } + try { + return await verify(passwordHash, plainPassword, ARGON2_OPTIONS) + } catch (error) { + return false + } +} + +function validatePasswordComplexity (plainPassword, policy) { + const errors = [] + + if (!plainPassword || plainPassword.length < policy.minPasswordLength) { + errors.push(`Password must be at least ${policy.minPasswordLength} characters`) + } + if (policy.requireUppercase && !/[A-Z]/.test(plainPassword || '')) { + errors.push('Password must contain an uppercase letter') + } + if (policy.requireLowercase && !/[a-z]/.test(plainPassword || '')) { + errors.push('Password must contain a lowercase letter') + } + if (policy.requireDigit && !/[0-9]/.test(plainPassword || '')) { + errors.push('Password must contain a digit') + } + + if (errors.length > 0) { + throw new Errors.ValidationError(errors.join('; ')) + } +} + +async function assertPasswordNotInHistory (plainPassword, policy, passwordHashes = []) { + if (!policy.passwordHistoryCount || policy.passwordHistoryCount <= 0) { + return + } + + for (const previousHash of passwordHashes.slice(0, policy.passwordHistoryCount)) { + if (previousHash && await verifyPassword(plainPassword, previousHash)) { + throw new Errors.ValidationError('Password was used recently and cannot be reused') + } + } +} + +async function recordPasswordHistory (previousHashes, previousPasswordHash, policy) { + if (!policy.passwordHistoryCount || policy.passwordHistoryCount <= 0 || !previousPasswordHash) { + return previousHashes + } + + return [previousPasswordHash, ...(previousHashes || [])].slice(0, policy.passwordHistoryCount) +} + +function generateRecoveryCodes (count = 10) { + const codes = [] + for (let i = 0; i < count; i++) { + codes.push(crypto.randomBytes(5).toString('hex')) + } + return codes +} + +async function hashRecoveryCodes (codes) { + const hashed = [] + for (const code of codes) { + hashed.push(await hashPassword(code)) + } + return JSON.stringify(hashed) +} + +async function verifyRecoveryCode (code, recoveryCodesHash) { + if (!code || !recoveryCodesHash) { + return false + } + + let storedHashes + try { + storedHashes = JSON.parse(recoveryCodesHash) + } catch (error) { + return false + } + + for (const storedHash of storedHashes) { + if (await verifyPassword(code, storedHash)) { + return true + } + } + return false +} + +async function consumeRecoveryCode (code, recoveryCodesHash) { + if (!code || !recoveryCodesHash) { + return null + } + + let storedHashes + try { + storedHashes = JSON.parse(recoveryCodesHash) + } catch (error) { + return null + } + + for (let index = 0; index < storedHashes.length; index++) { + if (await verifyPassword(code, storedHashes[index])) { + storedHashes.splice(index, 1) + return JSON.stringify(storedHashes) + } + } + return null +} + +module.exports = { + hashPassword, + verifyPassword, + validatePasswordComplexity, + assertPasswordNotInHistory, + recordPasswordHistory, + generateRecoveryCodes, + hashRecoveryCodes, + verifyRecoveryCode, + consumeRecoveryCode +} diff --git a/src/services/auth-policy-service.js b/src/services/auth-policy-service.js new file mode 100644 index 00000000..52c3da96 --- /dev/null +++ b/src/services/auth-policy-service.js @@ -0,0 +1,69 @@ +'use strict' + +const db = require('../data/models') +const { withTransaction } = require('../helpers/app-helper') + +const DEFAULT_POLICY = { + minPasswordLength: 12, + requireUppercase: true, + requireLowercase: true, + requireDigit: true, + passwordMaxAgeDays: 0, + passwordHistoryCount: 5, + maxFailedAttempts: 5, + lockoutDurationMinutes: 15, + accessTokenTtlSeconds: 900, + refreshTokenTtlSeconds: 604800, + refreshRotation: true, + maxConcurrentSessions: null +} + +async function getPolicy (transaction) { + const policy = await db.AuthPolicy.findByPk(1, withTransaction(transaction)) + if (!policy) { + return { ...DEFAULT_POLICY } + } + return policy.get({ plain: true }) +} + +function isAccountLocked (user, policy) { + if (!user.lockedUntil) { + return false + } + const lockedUntil = user.lockedUntil instanceof Date ? user.lockedUntil : new Date(user.lockedUntil) + if (lockedUntil <= new Date()) { + return false + } + return true +} + +async function recordFailedLogin (user, policy, transaction) { + const failedAttempts = (user.failedAttempts || 0) + 1 + const updates = { failedAttempts } + + if (failedAttempts >= policy.maxFailedAttempts) { + updates.lockedUntil = new Date(Date.now() + policy.lockoutDurationMinutes * 60 * 1000) + } + + await user.update(updates, withTransaction(transaction)) + return user.reload(withTransaction(transaction)) +} + +async function resetFailedLogin (user, transaction) { + if (!user.failedAttempts && !user.lockedUntil) { + return user + } + await user.update({ + failedAttempts: 0, + lockedUntil: null + }, withTransaction(transaction)) + return user.reload(withTransaction(transaction)) +} + +module.exports = { + DEFAULT_POLICY, + getPolicy, + isAccountLocked, + recordFailedLogin, + resetFailedLogin +} diff --git a/src/services/auth-token-service.js b/src/services/auth-token-service.js new file mode 100644 index 00000000..1e96b3a9 --- /dev/null +++ b/src/services/auth-token-service.js @@ -0,0 +1,143 @@ +'use strict' + +const crypto = require('crypto') +const { SignJWT } = require('jose') +const db = require('../data/models') +const { getOidcSettings } = require('../config/oidc') +const AuthJwks = require('../config/auth-jwks') +const { getPolicy } = require('./auth-policy-service') +const { withTransaction } = require('../helpers/app-helper') +const Errors = require('../helpers/errors') + +function hashRefreshToken (token) { + return crypto.createHash('sha256').update(token).digest('hex') +} + +function createOpaqueRefreshToken () { + return crypto.randomBytes(32).toString('base64url') +} + +async function issueAccessToken (user, groupNames, policy, transaction) { + const { issuerUrl, clientId } = getOidcSettings() + const { kid, signingKey } = await AuthJwks.getActiveSigningMaterial(transaction) + const ttlSeconds = policy.accessTokenTtlSeconds || 900 + + return new SignJWT({ + preferred_username: user.email, + email: user.email, + groups: groupNames + }) + .setProtectedHeader({ alg: 'RS256', kid }) + .setSubject(user.id) + .setIssuer(issuerUrl) + .setAudience(clientId) + .setIssuedAt() + .setExpirationTime(`${ttlSeconds}s`) + .sign(signingKey) +} + +async function persistRefreshToken (userId, refreshToken, familyId, policy, transaction) { + const ttlSeconds = policy.refreshTokenTtlSeconds || 604800 + await db.AuthRefreshToken.create({ + tokenHash: hashRefreshToken(refreshToken), + userId, + familyId, + expiresAt: new Date(Date.now() + ttlSeconds * 1000), + revoked: false + }, withTransaction(transaction)) +} + +async function issueTokenPair (user, groupNames, transaction) { + const policy = await getPolicy(transaction) + const accessToken = await issueAccessToken(user, groupNames, policy, transaction) + const refreshToken = createOpaqueRefreshToken() + const familyId = crypto.randomUUID() + + await persistRefreshToken(user.id, refreshToken, familyId, policy, transaction) + + return { accessToken, refreshToken } +} + +async function findValidRefreshToken (refreshToken, transaction) { + const tokenHash = hashRefreshToken(refreshToken) + const row = await db.AuthRefreshToken.findOne(withTransaction(transaction, { + where: { + tokenHash, + revoked: false + } + })) + + if (!row || row.expiresAt <= new Date()) { + return null + } + + return row +} + +async function revokeRefreshTokenFamily (familyId, transaction) { + await db.AuthRefreshToken.update({ revoked: true }, withTransaction(transaction, { + where: { familyId } + })) +} + +async function rotateRefreshToken (refreshToken, transaction) { + const row = await findValidRefreshToken(refreshToken, transaction) + if (!row) { + throw new Errors.InvalidCredentialsError() + } + + const policy = await getPolicy(transaction) + const user = await db.AuthUser.findByPk(row.userId, withTransaction(transaction, { + include: [{ + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }] + })) + + if (!user || user.deletedAt) { + throw new Errors.InvalidCredentialsError() + } + + if (policy.refreshRotation) { + await row.update({ revoked: true }, withTransaction(transaction)) + } + + const groupNames = (user.groups || []).map((group) => group.name) + const accessToken = await issueAccessToken(user, groupNames, policy, transaction) + let nextRefreshToken = refreshToken + + if (policy.refreshRotation) { + nextRefreshToken = createOpaqueRefreshToken() + await persistRefreshToken(user.id, nextRefreshToken, row.familyId, policy, transaction) + } + + return { + accessToken, + refreshToken: nextRefreshToken + } +} + +async function revokeRefreshToken (refreshToken, transaction) { + const row = await findValidRefreshToken(refreshToken, transaction) + if (!row) { + return + } + await row.update({ revoked: true }, withTransaction(transaction)) +} + +async function revokeAllUserRefreshTokens (userId, transaction) { + await db.AuthRefreshToken.update({ revoked: true }, withTransaction(transaction, { + where: { userId, revoked: false } + })) +} + +module.exports = { + issueAccessToken, + issueTokenPair, + rotateRefreshToken, + revokeRefreshToken, + revokeAllUserRefreshTokens, + revokeRefreshTokenFamily, + hashRefreshToken +} diff --git a/src/services/auth-user-service.js b/src/services/auth-user-service.js new file mode 100644 index 00000000..22a12842 --- /dev/null +++ b/src/services/auth-user-service.js @@ -0,0 +1,480 @@ +'use strict' + +const crypto = require('crypto') +const { Op } = require('sequelize') +const db = require('../data/models') +const Errors = require('../helpers/errors') +const { withTransaction } = require('../helpers/app-helper') +const TransactionDecorator = require('../decorators/transaction-decorator') +const { getAuthMode } = require('../config/oidc') +const AuthPolicyService = require('./auth-policy-service') +const AuthPasswordService = require('./auth-password-service') +const AuthTokenService = require('./auth-token-service') + +const RESET_TOKEN_TTL_SECONDS = 3600 + +function ensureEmbeddedMode () { + if (getAuthMode() !== 'embedded') { + throw new Errors.NotImplementedError() + } +} + +function normalizeEmail (email) { + return String(email || '').trim().toLowerCase() +} + +function parsePasswordHistory (user) { + if (!user.passwordHistoryHashes) { + return [] + } + try { + return JSON.parse(user.passwordHistoryHashes) + } catch (error) { + return [] + } +} + +function generateTemporaryPassword (policy) { + const upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ' + const lower = 'abcdefghijkmnopqrstuvwxyz' + const digits = '23456789' + const all = upper + lower + digits + const minLength = Math.max(policy.minPasswordLength || 12, 12) + + const chars = [ + upper[Math.floor(Math.random() * upper.length)], + lower[Math.floor(Math.random() * lower.length)], + digits[Math.floor(Math.random() * digits.length)] + ] + + while (chars.length < minLength) { + chars.push(all[Math.floor(Math.random() * all.length)]) + } + + for (let i = chars.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + const tmp = chars[i] + chars[i] = chars[j] + chars[j] = tmp + } + + return chars.join('') +} + +function formatUserResponse (user) { + const groups = (user.groups || []).map((group) => group.name) + return { + id: user.id, + email: user.email, + groups, + mustChangePassword: user.mustChangePassword, + mfaEnabled: Boolean(user.mfa && user.mfa.enabled), + isBootstrap: user.isBootstrap, + createdAt: user.createdAt, + updatedAt: user.updatedAt + } +} + +async function loadUserById (userId, transaction, includeDeleted = false) { + const where = { id: userId } + if (!includeDeleted) { + where.deletedAt = null + } + + return db.AuthUser.findOne(withTransaction(transaction, { + where, + include: [ + { + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }, + { + model: db.AuthMfa, + as: 'mfa' + } + ] + })) +} + +async function resolveGroupIds (groupNames, transaction) { + const normalizedNames = [...new Set((groupNames || []).map((name) => String(name).trim().toLowerCase()).filter(Boolean))] + if (normalizedNames.length === 0) { + return [] + } + + const groups = await db.AuthGroup.findAll(withTransaction(transaction, { + where: { + name: { + [Op.in]: normalizedNames + } + } + })) + + if (groups.length !== normalizedNames.length) { + const found = new Set(groups.map((group) => group.name)) + const missing = normalizedNames.filter((name) => !found.has(name)) + throw new Errors.ValidationError(`Unknown groups: ${missing.join(', ')}`) + } + + return groups.map((group) => group.id) +} + +async function setUserGroups (user, groupIds, transaction) { + await db.AuthUserGroup.destroy(withTransaction(transaction, { + where: { userId: user.id } + })) + + for (const groupId of groupIds) { + await db.AuthUserGroup.create({ + userId: user.id, + groupId + }, withTransaction(transaction)) + } +} + +async function updatePassword (user, newPassword, transaction) { + const policy = await AuthPolicyService.getPolicy(transaction) + AuthPasswordService.validatePasswordComplexity(newPassword, policy) + + const previousHashes = parsePasswordHistory(user) + await AuthPasswordService.assertPasswordNotInHistory(newPassword, policy, previousHashes) + + const nextHistory = await AuthPasswordService.recordPasswordHistory( + previousHashes, + user.passwordHash, + policy + ) + + await user.update({ + passwordHash: await AuthPasswordService.hashPassword(newPassword), + passwordHistoryHashes: JSON.stringify(nextHistory), + mustChangePassword: false, + failedAttempts: 0, + lockedUntil: null + }, withTransaction(transaction)) + + await AuthTokenService.revokeAllUserRefreshTokens(user.id, transaction) +} + +async function listUsers (transaction) { + const users = await db.AuthUser.findAll(withTransaction(transaction, { + where: { deletedAt: null }, + include: [ + { + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }, + { + model: db.AuthMfa, + as: 'mfa' + } + ], + order: [['email', 'ASC']] + })) + + return users.map(formatUserResponse) +} + +async function createUser ({ email, password, groups }, transaction) { + const normalizedEmail = normalizeEmail(email) + if (!normalizedEmail || !password) { + throw new Errors.ValidationError('email and password are required') + } + + const existing = await db.AuthUser.findOne(withTransaction(transaction, { + where: { email: normalizedEmail } + })) + if (existing && !existing.deletedAt) { + throw new Errors.ConflictError('A user with this email already exists') + } + + const policy = await AuthPolicyService.getPolicy(transaction) + AuthPasswordService.validatePasswordComplexity(password, policy) + + const groupIds = await resolveGroupIds(groups, transaction) + const userId = crypto.randomUUID() + const user = await db.AuthUser.create({ + id: userId, + email: normalizedEmail, + passwordHash: await AuthPasswordService.hashPassword(password), + mustChangePassword: false, + isBootstrap: false + }, withTransaction(transaction)) + + if (groupIds.length > 0) { + await setUserGroups(user, groupIds, transaction) + } + + return formatUserResponse(await loadUserById(user.id, transaction)) +} + +async function getUser (userId, transaction) { + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + return formatUserResponse(user) +} + +async function updateUser (userId, payload, transaction) { + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + + if (payload.email !== undefined) { + const normalizedEmail = normalizeEmail(payload.email) + if (!normalizedEmail) { + throw new Errors.ValidationError('email is required') + } + const duplicate = await db.AuthUser.findOne(withTransaction(transaction, { + where: { + email: normalizedEmail, + id: { [Op.ne]: user.id }, + deletedAt: null + } + })) + if (duplicate) { + throw new Errors.ConflictError('A user with this email already exists') + } + await user.update({ email: normalizedEmail }, withTransaction(transaction)) + } + + if (payload.groups !== undefined) { + const groupIds = await resolveGroupIds(payload.groups, transaction) + await setUserGroups(user, groupIds, transaction) + } + + return formatUserResponse(await loadUserById(user.id, transaction)) +} + +async function deleteUser (userId, actorUserId, transaction) { + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + if (user.isBootstrap) { + throw new Errors.ForbiddenError('Bootstrap admin user cannot be deleted') + } + if (actorUserId && actorUserId === user.id) { + throw new Errors.ForbiddenError('Users cannot delete their own account') + } + + await user.update({ deletedAt: new Date() }, withTransaction(transaction)) + await AuthTokenService.revokeAllUserRefreshTokens(user.id, transaction) + return { status: 'success' } +} + +async function resetPassword (userId, transaction) { + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + + const policy = await AuthPolicyService.getPolicy(transaction) + const temporaryPassword = generateTemporaryPassword(policy) + AuthPasswordService.validatePasswordComplexity(temporaryPassword, policy) + + await user.update({ + passwordHash: await AuthPasswordService.hashPassword(temporaryPassword), + mustChangePassword: true, + failedAttempts: 0, + lockedUntil: null + }, withTransaction(transaction)) + await AuthTokenService.revokeAllUserRefreshTokens(user.id, transaction) + + return { + temporaryPassword, + mustChangePassword: true + } +} + +async function resetToken (userId, transaction) { + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + + await db.AuthPasswordResetSession.destroy(withTransaction(transaction, { + where: { userId: user.id } + })) + + const resetTokenValue = crypto.randomUUID() + await db.AuthPasswordResetSession.create({ + id: resetTokenValue, + userId: user.id, + expiresAt: new Date(Date.now() + RESET_TOKEN_TTL_SECONDS * 1000) + }, withTransaction(transaction)) + + return { + resetToken: resetTokenValue, + expiresIn: RESET_TOKEN_TTL_SECONDS + } +} + +async function consumeResetToken (resetToken, transaction) { + const session = await db.AuthPasswordResetSession.findByPk(resetToken, withTransaction(transaction)) + if (!session || session.expiresAt <= new Date()) { + throw new Errors.InvalidCredentialsError('Invalid or expired reset token') + } + + await session.destroy(withTransaction(transaction)) + return session.userId +} + +async function changePassword (req, payload, transaction) { + if (payload.resetToken) { + const userId = await consumeResetToken(payload.resetToken, transaction) + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + if (!payload.newPassword) { + throw new Errors.ValidationError('newPassword is required') + } + await updatePassword(user, payload.newPassword, transaction) + return { status: 'success' } + } + + if (!req.headers.authorization) { + throw new Errors.AuthenticationError('Authentication required') + } + + const userId = req.kauth.grant.access_token.content.sub + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + + if (!payload.currentPassword || !payload.newPassword) { + throw new Errors.ValidationError('currentPassword and newPassword are required') + } + + if (!await AuthPasswordService.verifyPassword(payload.currentPassword, user.passwordHash)) { + throw new Errors.InvalidCredentialsError() + } + + await updatePassword(user, payload.newPassword, transaction) + return { status: 'success' } +} + +async function listGroups (transaction) { + const groups = await db.AuthGroup.findAll(withTransaction(transaction, { + order: [['name', 'ASC']] + })) + + return groups.map((group) => ({ + id: group.id, + name: group.name, + isSystem: group.isSystem, + createdAt: group.createdAt, + updatedAt: group.updatedAt + })) +} + +async function createGroup ({ name }, transaction) { + const normalizedName = String(name || '').trim().toLowerCase() + if (!normalizedName) { + throw new Errors.ValidationError('name is required') + } + if (SYSTEM_GROUP_NAMES.includes(normalizedName)) { + throw new Errors.ConflictError('A system group with this name already exists') + } + + const existing = await db.AuthGroup.findOne(withTransaction(transaction, { + where: { name: normalizedName } + })) + if (existing) { + throw new Errors.ConflictError('A group with this name already exists') + } + + const group = await db.AuthGroup.create({ + name: normalizedName, + isSystem: false + }, withTransaction(transaction)) + + return { + id: group.id, + name: group.name, + isSystem: group.isSystem, + createdAt: group.createdAt, + updatedAt: group.updatedAt + } +} + +async function getGroup (groupId, transaction) { + const group = await db.AuthGroup.findByPk(groupId, withTransaction(transaction)) + if (!group) { + throw new Errors.NotFoundError('Group not found') + } + + return { + id: group.id, + name: group.name, + isSystem: group.isSystem, + createdAt: group.createdAt, + updatedAt: group.updatedAt + } +} + +async function updateGroup (groupId, payload, transaction) { + const group = await db.AuthGroup.findByPk(groupId, withTransaction(transaction)) + if (!group) { + throw new Errors.NotFoundError('Group not found') + } + if (group.isSystem) { + throw new Errors.ForbiddenError('System groups cannot be modified') + } + + const normalizedName = String(payload.name || '').trim().toLowerCase() + if (!normalizedName) { + throw new Errors.ValidationError('name is required') + } + + const duplicate = await db.AuthGroup.findOne(withTransaction(transaction, { + where: { + name: normalizedName, + id: { [Op.ne]: group.id } + } + })) + if (duplicate) { + throw new Errors.ConflictError('A group with this name already exists') + } + + await group.update({ name: normalizedName }, withTransaction(transaction)) + return getGroup(group.id, transaction) +} + +async function deleteGroup (groupId, transaction) { + const group = await db.AuthGroup.findByPk(groupId, withTransaction(transaction)) + if (!group) { + throw new Errors.NotFoundError('Group not found') + } + if (group.isSystem) { + throw new Errors.ForbiddenError('System groups cannot be deleted') + } + + await group.destroy(withTransaction(transaction)) + return { status: 'success' } +} + +const SYSTEM_GROUP_NAMES = ['admin', 'sre', 'developer', 'viewer'] + +module.exports = { + ensureEmbeddedMode, + listUsers: TransactionDecorator.generateTransaction(listUsers), + createUser: TransactionDecorator.generateTransaction(createUser), + getUser: TransactionDecorator.generateTransaction(getUser), + updateUser: TransactionDecorator.generateTransaction(updateUser), + deleteUser: TransactionDecorator.generateTransaction(deleteUser), + resetPassword: TransactionDecorator.generateTransaction(resetPassword), + resetToken: TransactionDecorator.generateTransaction(resetToken), + changePassword: TransactionDecorator.generateTransaction(changePassword), + listGroups: TransactionDecorator.generateTransaction(listGroups), + createGroup: TransactionDecorator.generateTransaction(createGroup), + getGroup: TransactionDecorator.generateTransaction(getGroup), + updateGroup: TransactionDecorator.generateTransaction(updateGroup), + deleteGroup: TransactionDecorator.generateTransaction(deleteGroup) +} From fb2cc9aa3e726355c28daa4a08669b247f4bbfac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 05:41:13 +0300 Subject: [PATCH 44/75] Wire auth routes, user admin API, RBAC rules, and server bootstrap. --- docs/swagger.yaml | 695 +++++++++++++++++++++++++++- src/config/rbac-resources.yaml | 64 +++ src/config/rbac-system-roles.js | 4 +- src/controllers/auth-controller.js | 17 + src/controllers/user-controller.js | 40 +- src/controllers/users-controller.js | 95 ++++ src/routes/auth.js | 64 +++ src/routes/user.js | 214 +++++++++ src/routes/users.js | 139 ++++++ src/schemas/user.js | 91 +++- src/server.js | 135 ++++-- src/services/user-service.js | 134 ++++-- 12 files changed, 1588 insertions(+), 104 deletions(-) create mode 100644 src/controllers/auth-controller.js create mode 100644 src/controllers/users-controller.js create mode 100644 src/routes/auth.js create mode 100644 src/routes/users.js diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 79dbeaef..02c24784 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -3129,6 +3129,515 @@ paths: description: Not Authorized "500": description: Internal Server Error + /user/change-password: + post: + tags: + - User + summary: Change password (authenticated or via reset token) + operationId: changePassword + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ChangePasswordRequest" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + description: Bad Request + "401": + description: Not Authorized + "501": + description: Embedded auth mode only + /user/mfa/enroll: + post: + tags: + - User + summary: Begin MFA enrollment (embedded mode) + operationId: enrollMfa + security: + - authToken: [] + description: Start TOTP enrollment for the authenticated user. + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/MfaEnrollResponse" + "401": + description: Not Authorized + "501": + description: Embedded auth mode only + /user/mfa/confirm: + post: + tags: + - User + summary: Confirm MFA enrollment with TOTP code + operationId: confirmMfa + security: + - authToken: [] + description: Complete TOTP enrollment for the authenticated user. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/MfaConfirmRequest" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + description: Bad Request + "401": + description: Invalid credentials or enrollment token + "501": + description: Embedded auth mode only + /user/mfa: + delete: + tags: + - User + summary: Disable MFA for the current user + operationId: disableMfa + security: + - authToken: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/MfaDisableRequest" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + description: Bad Request + "401": + description: Not Authorized + "404": + description: Not Found + "501": + description: Embedded auth mode only + /user/oauth/authorize: + get: + tags: + - User + summary: Start external IdP OAuth authorization (BFF) + operationId: oauthAuthorize + description: Redirects the browser to the external OIDC authorization endpoint. External auth mode only. + responses: + "302": + description: Redirect to external IdP + "401": + description: Not Authorized + "501": + description: External auth mode only + /user/oauth/callback: + get: + tags: + - User + summary: OAuth authorization callback (BFF) + operationId: oauthCallback + description: > + Exchanges the authorization code for tokens. When VIEWER_URL is configured, + redirects to `{viewerUrl}/login#accessToken=...&refreshToken=...`. Otherwise + returns JSON `{ accessToken, refreshToken }`. + responses: + "200": + description: Token response (when VIEWER_URL is not configured) + content: + application/json: + schema: + $ref: "#/components/schemas/LoginSuccessResponse" + "302": + description: Redirect to ECN Viewer login with tokens in URL fragment + "401": + description: OAuth session invalid or authorization failed + "501": + description: External auth mode only + /auth/migration/export: + post: + tags: + - Auth Admin + summary: Export embedded auth users for external IdP migration + operationId: exportAuthMigration + description: Admin-only. Exports user UUIDs, emails, and group memberships. No secrets or password hashes. + security: + - authToken: [] + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AuthMigrationExportResponse" + "401": + description: Not Authorized + "403": + description: Forbidden + "501": + description: Embedded auth mode only + /auth/jwks/rotate: + post: + tags: + - Auth Admin + summary: Rotate embedded OIDC signing keys (manual) + operationId: rotateAuthJwks + description: > + Admin-only. Generates a new JWKS signing key and deactivates the previous active key. + Restart Controller pods after rotation so the embedded issuer loads the new key. + security: + - authToken: [] + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AuthJwksRotateResponse" + "401": + description: Not Authorized + "403": + description: Forbidden + "501": + description: Embedded auth mode only + /users: + get: + tags: + - Auth Users + summary: List embedded auth users + operationId: listAuthUsers + security: + - authToken: [] + responses: + "200": + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/AuthUserResponse" + "401": + description: Not Authorized + "501": + description: Embedded auth mode only + post: + tags: + - Auth Users + summary: Create embedded auth user + operationId: createAuthUser + security: + - authToken: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AuthUserCreateRequest" + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/AuthUserResponse" + "400": + description: Bad Request + "401": + description: Not Authorized + "409": + description: Conflict + "501": + description: Embedded auth mode only + "/users/{id}": + get: + tags: + - Auth Users + summary: Get embedded auth user + operationId: getAuthUser + security: + - authToken: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AuthUserResponse" + "401": + description: Not Authorized + "404": + description: Not Found + "501": + description: Embedded auth mode only + patch: + tags: + - Auth Users + summary: Update embedded auth user + operationId: updateAuthUser + security: + - authToken: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AuthUserUpdateRequest" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AuthUserResponse" + "400": + description: Bad Request + "401": + description: Not Authorized + "404": + description: Not Found + "501": + description: Embedded auth mode only + delete: + tags: + - Auth Users + summary: Soft-delete embedded auth user + operationId: deleteAuthUser + security: + - authToken: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "204": + description: Deleted + "401": + description: Not Authorized + "403": + description: Forbidden + "404": + description: Not Found + "501": + description: Embedded auth mode only + "/users/{id}/reset-password": + post: + tags: + - Auth Users + summary: Reset user password to a temporary value + operationId: resetAuthUserPassword + security: + - authToken: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AuthUserResetPasswordResponse" + "401": + description: Not Authorized + "404": + description: Not Found + "501": + description: Embedded auth mode only + "/users/{id}/reset-token": + post: + tags: + - Auth Users + summary: Issue a one-time password reset token + operationId: resetAuthUserToken + security: + - authToken: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AuthUserResetTokenResponse" + "401": + description: Not Authorized + "404": + description: Not Found + "501": + description: Embedded auth mode only + /groups: + get: + tags: + - Auth Groups + summary: List auth groups + operationId: listAuthGroups + security: + - authToken: [] + responses: + "200": + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/AuthGroupResponse" + "401": + description: Not Authorized + "501": + description: Embedded auth mode only + post: + tags: + - Auth Groups + summary: Create custom auth group + operationId: createAuthGroup + security: + - authToken: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AuthGroupCreateRequest" + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/AuthGroupResponse" + "400": + description: Bad Request + "401": + description: Not Authorized + "409": + description: Conflict + "501": + description: Embedded auth mode only + "/groups/{id}": + get: + tags: + - Auth Groups + summary: Get auth group + operationId: getAuthGroup + security: + - authToken: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AuthGroupResponse" + "401": + description: Not Authorized + "404": + description: Not Found + "501": + description: Embedded auth mode only + patch: + tags: + - Auth Groups + summary: Update custom auth group + operationId: updateAuthGroup + security: + - authToken: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AuthGroupUpdateRequest" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AuthGroupResponse" + "400": + description: Bad Request + "401": + description: Not Authorized + "403": + description: Forbidden + "404": + description: Not Found + "501": + description: Embedded auth mode only + delete: + tags: + - Auth Groups + summary: Delete custom auth group + operationId: deleteAuthGroup + security: + - authToken: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + "204": + description: Deleted + "401": + description: Not Authorized + "403": + description: Forbidden + "404": + description: Not Found + "501": + description: Embedded auth mode only /secrets: post: tags: @@ -7981,18 +8490,44 @@ components: required: - email - password - - totp - RefreshRequest: - type: string - required: - - refreshToken properties: email: type: string password: type: string totp: - type: string + type: string + description: TOTP or recovery code when MFA is enabled (optional otherwise; CLI may send empty string) + MfaEnrollResponse: + type: object + properties: + secret: + type: string + otpauthUrl: + type: string + MfaConfirmRequest: + type: object + required: + - code + properties: + code: + type: string + MfaDisableRequest: + type: object + required: + - password + properties: + password: + type: string + code: + type: string + RefreshRequest: + type: object + required: + - refreshToken + properties: + refreshToken: + type: string LoginSuccessResponse: type: object required: @@ -8038,6 +8573,154 @@ components: type: string email: type: string + ChangePasswordRequest: + type: object + required: + - newPassword + properties: + currentPassword: + type: string + newPassword: + type: string + resetToken: + type: string + StatusResponse: + type: object + properties: + status: + type: string + AuthUserCreateRequest: + type: object + required: + - email + - password + properties: + email: + type: string + password: + type: string + groups: + type: array + items: + type: string + AuthUserUpdateRequest: + type: object + properties: + email: + type: string + groups: + type: array + items: + type: string + AuthUserResponse: + type: object + properties: + id: + type: string + email: + type: string + groups: + type: array + items: + type: string + mustChangePassword: + type: boolean + mfaEnabled: + type: boolean + isBootstrap: + type: boolean + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + AuthUserResetPasswordResponse: + type: object + properties: + temporaryPassword: + type: string + mustChangePassword: + type: boolean + AuthUserResetTokenResponse: + type: object + properties: + resetToken: + type: string + expiresIn: + type: integer + AuthGroupCreateRequest: + type: object + required: + - name + properties: + name: + type: string + AuthGroupUpdateRequest: + type: object + required: + - name + properties: + name: + type: string + AuthGroupResponse: + type: object + properties: + id: + type: integer + name: + type: string + isSystem: + type: boolean + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + AuthMigrationExportResponse: + type: object + properties: + exportedAt: + type: string + format: date-time + authMode: + type: string + enum: [embedded] + users: + type: array + items: + type: object + properties: + id: + type: string + email: + type: string + groups: + type: array + items: + type: string + groups: + type: array + items: + type: object + properties: + id: + type: integer + name: + type: string + isSystem: + type: boolean + AuthJwksRotateResponse: + type: object + properties: + kid: + type: string + rotatedAt: + type: string + format: date-time + restartRequired: + type: boolean VersionCommandResponse: type: object required: diff --git a/src/config/rbac-resources.yaml b/src/config/rbac-resources.yaml index 52f540e7..adbaaa81 100644 --- a/src/config/rbac-resources.yaml +++ b/src/config/rbac-resources.yaml @@ -666,6 +666,70 @@ resources: - path: /api/v3/user/logout methods: POST: [] + - path: /api/v3/user/change-password + methods: + POST: [patch] + - path: /api/v3/user/mfa/enroll + methods: + POST: [patch] + - path: /api/v3/user/mfa/confirm + methods: + POST: [patch] + - path: /api/v3/user/mfa + methods: + DELETE: [delete] + - path: /api/v3/user/oauth/authorize + methods: + GET: [] + - path: /api/v3/user/oauth/callback + methods: + GET: [] + + authAdmin: + basePath: /api/v3/auth + routes: + - path: /api/v3/auth/migration/export + methods: + POST: [create] + - path: /api/v3/auth/jwks/rotate + methods: + POST: [patch] + + authUsers: + basePath: /api/v3/users + routes: + - path: /api/v3/users + methods: + GET: [list] + POST: [create] + - path: /api/v3/users/:id + methods: + GET: [get] + PATCH: [patch] + DELETE: [delete] + resourceNameParam: id + - path: /api/v3/users/:id/reset-password + methods: + POST: [patch] + resourceNameParam: id + - path: /api/v3/users/:id/reset-token + methods: + POST: [patch] + resourceNameParam: id + + authGroups: + basePath: /api/v3/groups + routes: + - path: /api/v3/groups + methods: + GET: [list] + POST: [create] + - path: /api/v3/groups/:id + methods: + GET: [get] + PATCH: [patch] + DELETE: [delete] + resourceNameParam: id # Config management config: diff --git a/src/config/rbac-system-roles.js b/src/config/rbac-system-roles.js index 999c9404..061a91ed 100644 --- a/src/config/rbac-system-roles.js +++ b/src/config/rbac-system-roles.js @@ -48,7 +48,7 @@ module.exports = { rules: [ { apiGroups: [''], - resources: ['microservices', 'systemMicroservices', 'fogs', 'applications', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'tunnels', 'certificates', 'capabilities', 'serviceAccounts', 'events', 'users', 'config', 'controller', 'execSessions', 'systemExecSessions', 'logs', 'systemLogs'], + resources: ['microservices', 'systemMicroservices', 'fogs', 'applications', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'tunnels', 'certificates', 'capabilities', 'serviceAccounts', 'events', 'users', 'authUsers', 'authGroups', 'config', 'controller', 'execSessions', 'systemExecSessions', 'logs', 'systemLogs'], verbs: ['*'] }, { @@ -73,7 +73,7 @@ module.exports = { }, { apiGroups: [''], - resources: ['fogs', 'router', 'tunnels', 'users', 'config', 'roles', 'roleBindings', 'systemMicroservices', 'systemApplications', 'natsOperator', 'natsHub'], + resources: ['fogs', 'router', 'tunnels', 'users', 'authUsers', 'authGroups', 'config', 'roles', 'roleBindings', 'systemMicroservices', 'systemApplications', 'natsOperator', 'natsHub'], verbs: ['get', 'list'] } ] diff --git a/src/controllers/auth-controller.js b/src/controllers/auth-controller.js new file mode 100644 index 00000000..4d0e36cf --- /dev/null +++ b/src/controllers/auth-controller.js @@ -0,0 +1,17 @@ +'use strict' + +const AuthMigrationService = require('../services/auth-migration-service') +const AuthJwksService = require('../services/auth-jwks-service') + +const migrationExportEndPoint = async function () { + return AuthMigrationService.exportMigrationData() +} + +const jwksRotateEndPoint = async function () { + return AuthJwksService.rotateSigningKey() +} + +module.exports = { + migrationExportEndPoint, + jwksRotateEndPoint +} diff --git a/src/controllers/user-controller.js b/src/controllers/user-controller.js index 9cff7744..3d876be9 100644 --- a/src/controllers/user-controller.js +++ b/src/controllers/user-controller.js @@ -1,5 +1,5 @@ /* - * ******************************************************************************* + * ******************************************************************************* * * Copyright (c) 2023 Datasance Teknoloji A.S. * * * * This program and the accompanying materials are made available under the @@ -48,9 +48,45 @@ const userLogoutEndPoint = async function (req) { return UserService.logout(req, false) } +const enrollMfaEndPoint = async function (req) { + return UserService.enrollMfa(req, false) +} + +const confirmMfaEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.mfaConfirm) + return UserService.confirmMfa(req, false) +} + +const disableMfaEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.mfaDisable) + return UserService.disableMfa(req, false) +} + +const changePasswordEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.changePassword) + return UserService.changePassword(req, payload, false) +} + +const oauthAuthorizeEndPoint = async function (req) { + return UserService.oauthAuthorize(req, false) +} + +const oauthCallbackEndPoint = async function (req) { + return UserService.oauthCallback(req, false) +} + module.exports = { userLoginEndPoint: userLoginEndPoint, refreshTokenEndPoint: refreshTokenEndPoint, getUserProfileEndPoint: getUserProfileEndPoint, - userLogoutEndPoint: userLogoutEndPoint + userLogoutEndPoint: userLogoutEndPoint, + enrollMfaEndPoint: enrollMfaEndPoint, + confirmMfaEndPoint: confirmMfaEndPoint, + disableMfaEndPoint: disableMfaEndPoint, + changePasswordEndPoint: changePasswordEndPoint, + oauthAuthorizeEndPoint: oauthAuthorizeEndPoint, + oauthCallbackEndPoint: oauthCallbackEndPoint } diff --git a/src/controllers/users-controller.js b/src/controllers/users-controller.js new file mode 100644 index 00000000..ab9ccb8f --- /dev/null +++ b/src/controllers/users-controller.js @@ -0,0 +1,95 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const AuthUserService = require('../services/auth-user-service') +const Validator = require('../schemas') + +const listUsersEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.listUsers() +} + +const createUserEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + await Validator.validate(req.body, Validator.schemas.createAuthUser) + return AuthUserService.createUser(req.body) +} + +const getUserEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.getUser(req.params.id) +} + +const updateUserEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + await Validator.validate(req.body, Validator.schemas.updateAuthUser) + return AuthUserService.updateUser(req.params.id, req.body) +} + +const deleteUserEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + const actorUserId = req.kauth.grant.access_token.content.sub + return AuthUserService.deleteUser(req.params.id, actorUserId) +} + +const resetPasswordEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.resetPassword(req.params.id) +} + +const resetTokenEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.resetToken(req.params.id) +} + +const listGroupsEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.listGroups() +} + +const createGroupEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + await Validator.validate(req.body, Validator.schemas.createAuthGroup) + return AuthUserService.createGroup(req.body) +} + +const getGroupEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.getGroup(parseInt(req.params.id, 10)) +} + +const updateGroupEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + await Validator.validate(req.body, Validator.schemas.updateAuthGroup) + return AuthUserService.updateGroup(parseInt(req.params.id, 10), req.body) +} + +const deleteGroupEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.deleteGroup(parseInt(req.params.id, 10)) +} + +module.exports = { + listUsersEndPoint, + createUserEndPoint, + getUserEndPoint, + updateUserEndPoint, + deleteUserEndPoint, + resetPasswordEndPoint, + resetTokenEndPoint, + listGroupsEndPoint, + createGroupEndPoint, + getGroupEndPoint, + updateGroupEndPoint, + deleteGroupEndPoint +} diff --git a/src/routes/auth.js b/src/routes/auth.js new file mode 100644 index 00000000..c3499a45 --- /dev/null +++ b/src/routes/auth.js @@ -0,0 +1,64 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const constants = require('../helpers/constants') +const AuthController = require('../controllers/auth-controller') +const ResponseDecorator = require('../decorators/response-decorator') +const Errors = require('../helpers/errors') +const logger = require('../logger') +const rbacMiddleware = require('../lib/rbac/middleware') + +function protectEmbeddedAdminRoute (handler, successCode) { + return async (req, res) => { + logger.apiReq(req) + + await rbacMiddleware.protect()(req, res, async () => { + const errorCodes = [ + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_FORBIDDEN, + errors: [Errors.ForbiddenError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const endpoint = ResponseDecorator.handleErrors(handler, successCode, errorCodes) + const responseObject = await endpoint(req) + const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token + ? req.kauth.grant.access_token.content.preferred_username + : 'system' + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes({ req, user, res, responseObject }) + }) + } +} + +module.exports = [ + { + method: 'post', + path: '/api/v3/auth/migration/export', + middleware: protectEmbeddedAdminRoute(AuthController.migrationExportEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'post', + path: '/api/v3/auth/jwks/rotate', + middleware: protectEmbeddedAdminRoute(AuthController.jwksRotateEndPoint, constants.HTTP_CODE_SUCCESS) + } +] diff --git a/src/routes/user.js b/src/routes/user.js index f900af89..c6732ac4 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -46,6 +46,98 @@ module.exports = [ // don't use req and responseObject as args, because they have password and token } }, + { + method: 'post', + path: '/api/v3/user/mfa/enroll', + middleware: async (req, res) => { + logger.apiReq(req) + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError, Errors.InvalidArgumentError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError] + }, + { + code: constants.HTTP_CODE_NOT_FOUND, + errors: [Errors.NotFoundError] + } + ] + + const enrollMfaEndPoint = ResponseDecorator.handleErrors(UserController.enrollMfaEndPoint, successCode, errorCodes) + const responseObject = await enrollMfaEndPoint(req) + + res + .status(responseObject.code) + .send(responseObject.body) + + logger.apiRes(req, { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/mfa/confirm', + middleware: async (req, res) => { + logger.apiReq(req) + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError, Errors.InvalidArgumentError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + } + ] + + const confirmMfaEndPoint = ResponseDecorator.handleErrors(UserController.confirmMfaEndPoint, successCode, errorCodes) + const responseObject = await confirmMfaEndPoint(req) + + res + .status(responseObject.code) + .send(responseObject.body) + + logger.apiRes(req, { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'delete', + path: '/api/v3/user/mfa', + middleware: async (req, res) => { + logger.apiReq(req) + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError, Errors.InvalidArgumentError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_FOUND, + errors: [Errors.NotFoundError] + } + ] + + const disableMfaEndPoint = ResponseDecorator.handleErrors(UserController.disableMfaEndPoint, successCode, errorCodes) + const responseObject = await disableMfaEndPoint(req) + + res + .status(responseObject.code) + .send(responseObject.body) + + logger.apiRes(req, { args: { statusCode: responseObject.code } }) + } + }, { method: 'post', path: '/api/v3/user/refresh', @@ -75,6 +167,42 @@ module.exports = [ // don't use req and responseObject as args, because they have password and token } }, + { + method: 'post', + path: '/api/v3/user/change-password', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/change-password') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError, Errors.InvalidArgumentError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_FOUND, + errors: [Errors.NotFoundError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const changePasswordEndPoint = ResponseDecorator.handleErrors(UserController.changePasswordEndPoint, successCode, errorCodes) + const responseObject = await changePasswordEndPoint(req) + + res + .status(responseObject.code) + .send(responseObject.body) + + logger.apiRes('POST /api/v3/user/change-password', { args: { statusCode: responseObject.code } }) + } + }, { method: 'get', path: '/api/v3/user/profile', @@ -124,5 +252,91 @@ module.exports = [ .status(responseObject.code) .send() } + }, + { + method: 'get', + path: '/api/v3/user/oauth/authorize', + middleware: async (req, res) => { + logger.apiReq('GET /api/v3/user/oauth/authorize') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const oauthAuthorizeEndPoint = ResponseDecorator.handleErrors( + UserController.oauthAuthorizeEndPoint, + successCode, + errorCodes + ) + const responseObject = await oauthAuthorizeEndPoint(req) + if (responseObject.code === constants.HTTP_CODE_SUCCESS) { + res.redirect(302, responseObject.body.redirectUrl) + logger.apiRes('GET /api/v3/user/oauth/authorize', { args: { statusCode: 302 } }) + return + } + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('GET /api/v3/user/oauth/authorize', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'get', + path: '/api/v3/user/oauth/callback', + middleware: async (req, res) => { + logger.apiReq('GET /api/v3/user/oauth/callback') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const oauthCallbackEndPoint = ResponseDecorator.handleErrors( + UserController.oauthCallbackEndPoint, + successCode, + errorCodes + ) + + const responseObject = await oauthCallbackEndPoint(req) + + if (responseObject.code !== constants.HTTP_CODE_SUCCESS) { + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('GET /api/v3/user/oauth/callback', { args: { statusCode: responseObject.code } }) + return + } + + const { tokens, viewerUrl } = responseObject.body + + if (viewerUrl) { + const fragment = new URLSearchParams() + fragment.set('accessToken', tokens.accessToken) + if (tokens.refreshToken) { + fragment.set('refreshToken', tokens.refreshToken) + } + res.redirect(302, `${viewerUrl}/login#${fragment.toString()}`) + logger.apiRes('GET /api/v3/user/oauth/callback', { args: { statusCode: 302 } }) + return + } + + res + .status(responseObject.code) + .send(tokens) + + logger.apiRes('GET /api/v3/user/oauth/callback', { args: { statusCode: responseObject.code } }) + } } ] diff --git a/src/routes/users.js b/src/routes/users.js new file mode 100644 index 00000000..d77c3a9d --- /dev/null +++ b/src/routes/users.js @@ -0,0 +1,139 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const constants = require('../helpers/constants') +const UsersController = require('../controllers/users-controller') +const ResponseDecorator = require('../decorators/response-decorator') +const Errors = require('../helpers/errors') +const logger = require('../logger') +const rbacMiddleware = require('../lib/rbac/middleware') + +function embeddedErrorCodes (successCode) { + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError, Errors.InvalidArgumentError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_FORBIDDEN, + errors: [Errors.ForbiddenError] + }, + { + code: constants.HTTP_CODE_NOT_FOUND, + errors: [Errors.NotFoundError] + }, + { + code: constants.HTTP_CODE_DUPLICATE_PROPERTY, + errors: [Errors.ConflictError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + if (successCode === constants.HTTP_CODE_NO_CONTENT) { + return errorCodes + } + + return errorCodes +} + +function protectEmbeddedRoute (handler, successCode) { + return async (req, res) => { + logger.apiReq(req) + + await rbacMiddleware.protect()(req, res, async () => { + const endpoint = ResponseDecorator.handleErrors(handler, successCode, embeddedErrorCodes(successCode)) + const responseObject = await endpoint(req) + const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token + ? req.kauth.grant.access_token.content.preferred_username + : 'system' + + if (responseObject.code === constants.HTTP_CODE_NO_CONTENT) { + res.status(responseObject.code).send() + } else { + res.status(responseObject.code).send(responseObject.body) + } + + logger.apiRes({ req, user, res, responseObject }) + }) + } +} + +module.exports = [ + { + method: 'get', + path: '/api/v3/users', + middleware: protectEmbeddedRoute(UsersController.listUsersEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'post', + path: '/api/v3/users', + middleware: protectEmbeddedRoute(UsersController.createUserEndPoint, constants.HTTP_CODE_CREATED) + }, + { + method: 'get', + path: '/api/v3/users/:id', + middleware: protectEmbeddedRoute(UsersController.getUserEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'patch', + path: '/api/v3/users/:id', + middleware: protectEmbeddedRoute(UsersController.updateUserEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'delete', + path: '/api/v3/users/:id', + middleware: protectEmbeddedRoute(UsersController.deleteUserEndPoint, constants.HTTP_CODE_NO_CONTENT) + }, + { + method: 'post', + path: '/api/v3/users/:id/reset-password', + middleware: protectEmbeddedRoute(UsersController.resetPasswordEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'post', + path: '/api/v3/users/:id/reset-token', + middleware: protectEmbeddedRoute(UsersController.resetTokenEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'get', + path: '/api/v3/groups', + middleware: protectEmbeddedRoute(UsersController.listGroupsEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'post', + path: '/api/v3/groups', + middleware: protectEmbeddedRoute(UsersController.createGroupEndPoint, constants.HTTP_CODE_CREATED) + }, + { + method: 'get', + path: '/api/v3/groups/:id', + middleware: protectEmbeddedRoute(UsersController.getGroupEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'patch', + path: '/api/v3/groups/:id', + middleware: protectEmbeddedRoute(UsersController.updateGroupEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'delete', + path: '/api/v3/groups/:id', + middleware: protectEmbeddedRoute(UsersController.deleteGroupEndPoint, constants.HTTP_CODE_NO_CONTENT) + } +] diff --git a/src/schemas/user.js b/src/schemas/user.js index d069afeb..c2f1ecb7 100644 --- a/src/schemas/user.js +++ b/src/schemas/user.js @@ -37,7 +37,96 @@ const refresh = { additionalProperties: true } +const mfaConfirm = { + id: '/mfaConfirm', + type: 'object', + properties: { + code: { type: 'string' } + }, + required: ['code'], + additionalProperties: true +} + +const mfaDisable = { + id: '/mfaDisable', + type: 'object', + properties: { + password: { type: 'string' }, + code: { type: 'string' } + }, + required: ['password', 'code'], + additionalProperties: true +} + +const changePassword = { + id: '/changePassword', + type: 'object', + properties: { + currentPassword: { type: 'string' }, + newPassword: { type: 'string' }, + resetToken: { type: 'string' } + }, + required: ['newPassword'], + additionalProperties: true +} + +const createAuthUser = { + id: '/createAuthUser', + type: 'object', + properties: { + email: { + type: 'string', + pattern: '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + + '\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$' + }, + password: { type: 'string' }, + groups: { + type: 'array', + items: { type: 'string' } + } + }, + required: ['email', 'password'], + additionalProperties: true +} + +const updateAuthUser = { + id: '/updateAuthUser', + type: 'object', + properties: { + email: { + type: 'string', + pattern: '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + + '\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$' + }, + groups: { + type: 'array', + items: { type: 'string' } + } + }, + additionalProperties: true +} + +const createAuthGroup = { + id: '/createAuthGroup', + type: 'object', + properties: { + name: { type: 'string', minLength: 1 } + }, + required: ['name'], + additionalProperties: true +} + +const updateAuthGroup = { + id: '/updateAuthGroup', + type: 'object', + properties: { + name: { type: 'string', minLength: 1 } + }, + required: ['name'], + additionalProperties: true +} + module.exports = { - mainSchemas: [login, refresh], + mainSchemas: [login, refresh, mfaConfirm, mfaDisable, changePassword, createAuthUser, updateAuthUser, createAuthGroup, updateAuthGroup], innerSchemas: [] } diff --git a/src/server.js b/src/server.js index 23efd4fd..ac23551c 100755 --- a/src/server.js +++ b/src/server.js @@ -39,13 +39,46 @@ initialize().then(() => { // Initialize session and OIDC bearer validation after config is loaded const session = require('express-session') - const { initOidc, getOidcMiddleware, getMemoryStore, getOidcSettings } = require('./config/oidc.js') + const { initOidc, getOidcMiddleware, getMemoryStore, getAuthMode, isAuthConfigured } = require('./config/oidc.js') const memoryStore = getMemoryStore() initOidc() const viewerApp = express() const app = express() + const trustProxy = process.env.TRUST_PROXY || config.get('server.trustProxy', false) + if (trustProxy) { + app.set('trust proxy', trustProxy === true ? 1 : trustProxy) + viewerApp.set('trust proxy', trustProxy === true ? 1 : trustProxy) + } + + function validateProductionPublicUrl () { + const devMode = process.env.DEV_MODE || config.get('server.devMode', true) + if (devMode) { + return + } + + const publicUrl = process.env.CONTROLLER_PUBLIC_URL || config.get('server.publicUrl') + const insecureAllowHttp = config.get('auth.insecureAllowHttp', false) + + if (!publicUrl) { + throw new Error('CONTROLLER_PUBLIC_URL is required in production mode') + } + + let parsedUrl + try { + parsedUrl = new URL(publicUrl) + } catch (error) { + throw new Error('CONTROLLER_PUBLIC_URL must be a valid URL') + } + + if (!insecureAllowHttp && parsedUrl.protocol !== 'https:') { + throw new Error('CONTROLLER_PUBLIC_URL must use https in production unless auth.insecureAllowHttp is true') + } + } + + validateProductionPublicUrl() + app.use(cors()) app.use(helmet()) @@ -115,21 +148,12 @@ initialize().then(() => { routes.forEach(registerRoute) } - fs.readdirSync(path.join(__dirname, 'routes')) - .forEach(setupMiddleware) - const jobs = [] const setupJobs = function (file) { jobs.push((require(path.join(__dirname, 'jobs', file)) || [])) } - fs.readdirSync(path.join(__dirname, 'jobs')) - .filter((file) => { - return (file.indexOf('.') !== 0) && (file.slice(-3) === '.js') - }) - .forEach(setupJobs) - function registerServers (api, viewer) { process.once('SIGTERM', async function (code) { console.log('SIGTERM received. Shutting down.') @@ -142,7 +166,7 @@ initialize().then(() => { } function startHttpServer (apps, ports, jobs) { - logger.info('SSL not configured, starting HTTP server.') + logger.info('TLS not configured, starting HTTP server.') const viewerServer = apps.viewer.listen(ports.viewer, function onStart (err) { if (err) { @@ -199,7 +223,7 @@ initialize().then(() => { registerServers(apiServer, viewerServer) } catch (e) { - logger.error('Error loading SSL certificates. Please check your configuration.') + logger.error('Error loading TLS certificates. Please check your configuration.') } } @@ -208,32 +232,20 @@ initialize().then(() => { const viewerPort = process.env.VIEWER_PORT || config.get('viewer.port') const viewerURL = process.env.VIEWER_URL || config.get('viewer.url') const controlPlane = process.env.CONTROL_PLANE || config.get('app.ControlPlane') + const publicUrl = (process.env.CONTROLLER_PUBLIC_URL || config.get('server.publicUrl') || '').replace(/\/$/, '') - // File-based SSL configuration - const sslKey = process.env.SSL_PATH_KEY || config.get('server.ssl.path.key') - const sslCert = process.env.SSL_PATH_CERT || config.get('server.ssl.path.cert') - const intermedKey = process.env.SSL_PATH_INTERMEDIATE_CERT || config.get('server.ssl.path.intermediateCert') - - // Base64 SSL configuration - const sslKeyBase64 = process.env.SSL_BASE64_KEY || config.get('server.ssl.base64.key') - const sslCertBase64 = process.env.SSL_BASE64_CERT || config.get('server.ssl.base64.cert') - const intermedKeyBase64 = process.env.SSL_BASE64_INTERMEDIATE_CERT || config.get('server.ssl.base64.intermediateCert') - - const hasFileBasedSSL = !devMode && sslKey && sslCert - const hasBase64SSL = !devMode && sslKeyBase64 && sslCertBase64 - - const { issuerUrl: oidcIssuerUrl } = getOidcSettings() - const oidcViewerClient = process.env.OIDC_VIEWER_CLIENT_ID || config.get('auth.viewerClient') - // ECN Viewer still reads keycloak* keys in controller-config.js; derive from OIDC issuer when possible - let viewerAuthUrl = oidcIssuerUrl || '' - let viewerAuthRealm = '' - if (oidcIssuerUrl) { - const realmMatch = oidcIssuerUrl.match(/^(.*)\/realms\/([^/]+)\/?$/) - if (realmMatch) { - viewerAuthUrl = `${realmMatch[1]}/` - viewerAuthRealm = realmMatch[2] - } - } + // File-based TLS configuration + const tlsKey = process.env.TLS_PATH_KEY || config.get('server.tls.path.key') + const tlsCert = process.env.TLS_PATH_CERT || config.get('server.tls.path.cert') + const intermedKey = process.env.TLS_PATH_INTERMEDIATE_CERT || config.get('server.tls.path.intermediateCert') + + // Base64 TLS configuration + const tlsKeyBase64 = process.env.TLS_BASE64_KEY || config.get('server.tls.base64.key') + const tlsCertBase64 = process.env.TLS_BASE64_CERT || config.get('server.tls.base64.cert') + const intermedKeyBase64 = process.env.TLS_BASE64_INTERMEDIATE_CERT || config.get('server.tls.base64.intermediateCert') + + const hasFileBasedTLS = !devMode && tlsKey && tlsCert + const hasBase64TLS = !devMode && tlsKeyBase64 && tlsCertBase64 viewerApp.use('/', ecnViewer.middleware(express)) @@ -258,18 +270,41 @@ initialize().then(() => { } }) } + + if (getAuthMode() === 'embedded' && isAuthConfigured()) { + const { runBootstrap } = require('./services/auth-bootstrap-service') + await runBootstrap() + const { initEmbeddedIssuer } = require('./config/embedded-oidc.js') + await initEmbeddedIssuer(app, { db }) + } + + fs.readdirSync(path.join(__dirname, 'routes')) + .forEach(setupMiddleware) + + fs.readdirSync(path.join(__dirname, 'jobs')) + .filter((file) => { + return (file.indexOf('.') !== 0) && (file.slice(-3) === '.js') + }) + .forEach(setupJobs) + // Set up controller-config.js for ECN Viewer const ecnViewerControllerConfigFilePath = path.join(__dirname, '..', 'node_modules', '@datasance', 'ecn-viewer', 'build', 'controller-config.js') const ecnViewerControllerConfig = { - port: apiPort, - user: {}, - controllerDevMode: devMode, - keycloakUrl: viewerAuthUrl, - keycloakRealm: viewerAuthRealm, - keycloakClientId: oidcViewerClient + apiPort, + auth: { + loginUrl: '/api/v3/user/login', + refreshUrl: '/api/v3/user/refresh', + logoutUrl: '/api/v3/user/logout', + profileUrl: '/api/v3/user/profile', + changePasswordUrl: '/api/v3/user/change-password', + oauthAuthorizeUrl: '/api/v3/user/oauth/authorize' + } + } + if (publicUrl) { + ecnViewerControllerConfig.publicUrl = publicUrl } if (viewerURL) { - ecnViewerControllerConfig.url = viewerURL + ecnViewerControllerConfig.viewerUrl = viewerURL } if (controlPlane) { ecnViewerControllerConfig.controlPlane = controlPlane @@ -282,22 +317,22 @@ initialize().then(() => { initState() .then(() => { - if (hasFileBasedSSL) { + if (hasFileBasedTLS) { startHttpsServer( { api: app, viewer: viewerApp }, { api: apiPort, viewer: viewerPort }, - sslKey, - sslCert, + tlsKey, + tlsCert, intermedKey, jobs, false ) - } else if (hasBase64SSL) { + } else if (hasBase64TLS) { startHttpsServer( { api: app, viewer: viewerApp }, { api: apiPort, viewer: viewerPort }, - sslKeyBase64, - sslCertBase64, + tlsKeyBase64, + tlsCertBase64, intermedKeyBase64, jobs, true diff --git a/src/services/user-service.js b/src/services/user-service.js index 4e9e3444..90441244 100644 --- a/src/services/user-service.js +++ b/src/services/user-service.js @@ -13,7 +13,6 @@ const Errors = require('../helpers/errors') const TransactionDecorator = require('../decorators/transaction-decorator') -const config = require('../config') const { genericGrantRequest, refreshTokenGrant, @@ -21,22 +20,11 @@ const { tokenRevocation } = require('openid-client') const { decodeJwt } = require('jose') -const { getOidcConfiguration, isAuthConfigured } = require('../config/oidc') - -const isDevMode = config.get('server.devMode', true) - -const mockUser = { - preferred_username: 'dev-user', - email: 'dev@example.com', - realm_access: { - roles: ['SRE', 'Developer', 'Viewer'] - } -} - -const mockToken = { - access_token: 'mock-access-token', - refresh_token: 'mock-refresh-token' -} +const { getOidcConfiguration, isAuthConfigured, getAuthMode } = require('../config/oidc') +const AuthLoginService = require('./auth-login-service') +const AuthMfaService = require('./auth-mfa-service') +const AuthUserService = require('./auth-user-service') +const AuthOauthService = require('./auth-oauth-service') function mapOidcError (error) { const description = error.error_description || error.message || 'Invalid credentials' @@ -50,25 +38,23 @@ function tokensFromResponse (tokenResponse) { } } -function ensureAuthOrDev () { - if (!isAuthConfigured() && isDevMode) { - return 'dev' - } - - if (!isAuthConfigured() && !isDevMode) { +function ensureAuthConfigured () { + if (!isAuthConfigured()) { throw new Error('Auth is not configured for this cluster. Please contact your administrator.') } +} - return 'oidc' +function ensureEmbeddedMode () { + if (getAuthMode() !== 'embedded') { + throw new Errors.InvalidArgumentError('This endpoint is only available in embedded auth mode') + } } const login = async function (credentials, isCLI, transaction) { - const mode = ensureAuthOrDev() - if (mode === 'dev') { - return { - accessToken: mockToken.access_token, - refreshToken: mockToken.refresh_token - } + ensureAuthConfigured() + + if (getAuthMode() === 'embedded') { + return AuthLoginService.login(credentials, transaction) } try { @@ -89,12 +75,10 @@ const login = async function (credentials, isCLI, transaction) { } const refresh = async function (credentials, isCLI, transaction) { - const mode = ensureAuthOrDev() - if (mode === 'dev') { - return { - accessToken: mockToken.access_token, - refreshToken: mockToken.refresh_token - } + ensureAuthConfigured() + + if (getAuthMode() === 'embedded') { + return AuthLoginService.refresh(credentials, transaction) } try { @@ -107,9 +91,10 @@ const refresh = async function (credentials, isCLI, transaction) { } const profile = async function (req, isCLI, transaction) { - const mode = ensureAuthOrDev() - if (mode === 'dev') { - return mockUser + ensureAuthConfigured() + + if (getAuthMode() === 'embedded') { + return AuthLoginService.profile(req, transaction) } const accessToken = req.headers.authorization.replace('Bearer ', '') @@ -132,9 +117,10 @@ const profile = async function (req, isCLI, transaction) { } const logout = async function (req, isCLI, transaction) { - const mode = ensureAuthOrDev() - if (mode === 'dev') { - return { status: 'success' } + ensureAuthConfigured() + + if (getAuthMode() === 'embedded') { + return AuthLoginService.logout(req, transaction) } const accessToken = req.headers.authorization.replace('Bearer ', '') @@ -152,9 +138,71 @@ const logout = async function (req, isCLI, transaction) { return { status: 'success' } } +const enrollMfa = async function (req, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + + if (!req.kauth || !req.kauth.grant || !req.kauth.grant.access_token) { + throw new Errors.AuthenticationError('Authentication required') + } + + const userId = req.kauth.grant.access_token.content.sub + return AuthMfaService.enrollMfa(userId, transaction) +} + +const confirmMfa = async function (req, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + + if (!req.kauth || !req.kauth.grant || !req.kauth.grant.access_token) { + throw new Errors.AuthenticationError('Authentication required') + } + + const userId = req.kauth.grant.access_token.content.sub + return AuthMfaService.confirmMfa(userId, req.body.code, transaction) +} + +const disableMfa = async function (req, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + + const userId = req.kauth.grant.access_token.content.sub + return AuthMfaService.disableMfa(userId, req.body.password, req.body.code, transaction) +} + +const changePassword = async function (req, payload, isCLI, transaction) { + ensureAuthConfigured() + + if (getAuthMode() === 'embedded') { + if (payload.resetToken) { + return AuthUserService.changePassword(req, payload, transaction) + } + ensureEmbeddedMode() + return AuthUserService.changePassword(req, payload, transaction) + } + + throw new Errors.NotImplementedError('Password change is only supported in embedded auth mode') +} + +const oauthAuthorize = async function (req, isCLI, transaction) { + ensureAuthConfigured() + return AuthOauthService.authorize(req) +} + +const oauthCallback = async function (req, isCLI, transaction) { + ensureAuthConfigured() + return AuthOauthService.callback(req) +} + module.exports = { login: TransactionDecorator.generateTransaction(login), refresh: TransactionDecorator.generateTransaction(refresh), profile: TransactionDecorator.generateTransaction(profile), - logout: TransactionDecorator.generateTransaction(logout) + logout: TransactionDecorator.generateTransaction(logout), + enrollMfa: TransactionDecorator.generateTransaction(enrollMfa), + confirmMfa: TransactionDecorator.generateTransaction(confirmMfa), + disableMfa: TransactionDecorator.generateTransaction(disableMfa), + changePassword: TransactionDecorator.generateTransaction(changePassword), + oauthAuthorize: TransactionDecorator.generateTransaction(oauthAuthorize), + oauthCallback: TransactionDecorator.generateTransaction(oauthCallback) } From b77efcf68cbf7bb59d8aa39508626f7d9c38084b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 05:41:19 +0300 Subject: [PATCH 45/75] Add embedded auth integration tests and remove mock OIDC harness. --- test/oidc/README.md | 85 ++-- test/oidc/run-embedded-smoke.js | 43 ++ test/oidc/run-mock-provider.js | 49 --- test/src/config/embedded-oidc.test.js | 156 +++++++ test/src/config/oidc.test.js | 104 +++-- test/src/lib/rbac/middleware-oidc.test.js | 51 +-- test/src/services/auth-integration.test.js | 246 +++++++++++ test/src/services/auth-login.test.js | 265 ++++++++++++ test/src/services/user-service-oidc.test.js | 152 +++---- test/src/support/embedded-auth-smoke.test.js | 52 +++ test/src/support/mock-oidc-smoke.test.js | 35 -- test/support/embedded-auth-harness.js | 414 +++++++++++++++++++ test/support/embedded-auth-smoke.js | 32 ++ test/support/mock-oidc-provider.js | 262 ------------ test/support/oidc-smoke.js | 40 -- test/support/oidc-test-helpers.js | 23 +- 16 files changed, 1435 insertions(+), 574 deletions(-) create mode 100644 test/oidc/run-embedded-smoke.js delete mode 100644 test/oidc/run-mock-provider.js create mode 100644 test/src/config/embedded-oidc.test.js create mode 100644 test/src/services/auth-integration.test.js create mode 100644 test/src/services/auth-login.test.js create mode 100644 test/src/support/embedded-auth-smoke.test.js delete mode 100644 test/src/support/mock-oidc-smoke.test.js create mode 100644 test/support/embedded-auth-harness.js create mode 100644 test/support/embedded-auth-smoke.js delete mode 100644 test/support/mock-oidc-provider.js delete mode 100644 test/support/oidc-smoke.js diff --git a/test/oidc/README.md b/test/oidc/README.md index c8c269d3..248f2348 100644 --- a/test/oidc/README.md +++ b/test/oidc/README.md @@ -1,67 +1,86 @@ -# OIDC mock provider (Plan 8) +# Embedded auth dev smoke (Plan 8.1) -Generic OIDC smoke path for dev and CI. Replaces the legacy `MockKeycloak` dev shim with a -provider-agnostic mock that exercises the same discovery + JWKS + bearer validation flow as -production. +Local smoke path for embedded identity: Controller issues tokens from the in-process `/oidc` +issuer and validates Bearer JWTs via local JWKS. No mock OIDC provider. -## Unit tests +## Unit / integration tests -OIDC and RBAC middleware tests live under: +Auth tests live under: - `test/src/config/oidc.test.js` +- `test/src/config/embedded-oidc.test.js` +- `test/src/services/auth-login.test.js` +- `test/src/services/auth-integration.test.js` +- `test/src/services/user-service-oidc.test.js` - `test/src/lib/rbac/middleware-oidc.test.js` -- `test/src/support/mock-oidc-smoke.test.js` +- `test/src/support/embedded-auth-smoke.test.js` -Run only OIDC-related tests: +Run OIDC-related tests: ```bash nvm use 24 -node ./node_modules/mocha/bin/mocha.js test/src/config/oidc.test.js test/src/lib/rbac/middleware-oidc.test.js test/src/support/mock-oidc-smoke.test.js --require test/support/setup.js --ui bdd-lazy-var/global --grep 'OIDC|Mock OIDC' --exit +node ./node_modules/mocha/bin/mocha.js \ + test/src/config/oidc.test.js \ + test/src/config/embedded-oidc.test.js \ + test/src/services/auth-login.test.js \ + test/src/services/auth-integration.test.js \ + test/src/services/user-service-oidc.test.js \ + test/src/lib/rbac/middleware-oidc.test.js \ + test/src/support/embedded-auth-smoke.test.js \ + --require test/support/setup.js \ + --ui bdd-lazy-var/global \ + --grep 'OIDC|Embedded auth|RBAC middleware OIDC' \ + --exit ``` -## Local dev smoke +Test harness: `test/support/embedded-auth-harness.js` (in-memory auth store + embedded JWKS). -1. Start the mock issuer: +## Local dev smoke (embedded mode) + +1. Print recommended env: ```bash - node test/oidc/run-mock-provider.js + node test/oidc/run-embedded-smoke.js ``` -2. Export the printed `OIDC_*` variables in the shell where you run Controller. +2. Export the printed variables in the shell where you run Controller. -3. Ensure `server.devMode` is true (default in `config.yaml`) or set auth in yaml — the mock - env vars take precedence over an empty auth block. +3. For HTTP-only local runs, also set: -4. For the self-signed mock cert, also run: + ```bash + export AUTH_INSECURE_ALLOW_HTTP=true + ``` + +4. Start Controller: ```bash - export NODE_TLS_REJECT_UNAUTHORIZED=0 + npm run start-dev ``` -5. Start Controller (`npm run start-dev`) and call a RBAC-protected route with: +5. Login (bootstrap admin on first boot): ```bash - curl -H "Authorization: Bearer " http://localhost:51121/api/v3/... + curl -s -X POST http://localhost:51121/api/v3/user/login \ + -H "Content-Type: application/json" \ + -d '{"email":"admin@example.com","password":"ChangeMeSecure123!"}' ``` - Issue a token from Node (example): +6. Call a protected route with the returned `accessToken`: - ```javascript - const { MockOidcProvider } = require('./test/support/mock-oidc-provider') - const p = new MockOidcProvider({ clientId: '...', clientSecret: '...' }) - await p.start() - const token = await p.issueAccessToken({ preferred_username: 'smoke-user', roles: ['sre'] }) + ```bash + curl -H "Authorization: Bearer " http://localhost:51121/api/v3/user/profile ``` -6. Without a bearer token, protected routes should return `401`. With a valid token, RBAC - applies from `rbac-resources.yaml` and RoleBindings as before. +7. Admin accounts require MFA to be enrolled before login succeeds (except **bootstrap admin** with `isBootstrap: true`). Login accepts optional `totp` on the same request; missing or invalid MFA returns **401**. Enroll/confirm via Bearer on `/user/mfa/enroll` and `/user/mfa/confirm`. -## Real provider smoke +## External IdP smoke -Point the same env vars at any OIDC issuer (Keycloak, Auth0, etc.): +Point env at any OIDC issuer: -- `OIDC_ISSUER_URL` — issuer URL (discovery document at `/.well-known/openid-configuration`) -- `OIDC_CLIENT_ID` — confidential client for Controller API -- `OIDC_CLIENT_SECRET` — client secret +- `AUTH_MODE=external` +- `OIDC_ISSUER_URL` — full issuer URL +- `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` — confidential client +- `CONTROLLER_PUBLIC_URL` — canonical external URL -No Keycloak-specific env vars are required. +Embedded-only routes (`/api/v3/users`, migration export, JWKS rotate) return **501** in +external mode. diff --git a/test/oidc/run-embedded-smoke.js b/test/oidc/run-embedded-smoke.js new file mode 100644 index 00000000..098aa41a --- /dev/null +++ b/test/oidc/run-embedded-smoke.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node +/* + * Embedded auth dev smoke helper. + * + * Usage: + * node test/oidc/run-embedded-smoke.js + * + * Export the printed env vars, set bootstrap admin credentials, then start Controller. + */ + +const EMBEDDED_PUBLIC_URL = 'https://controller.test' +const EMBEDDED_CLIENT_ID = 'controller' + +function main () { + console.log('Embedded auth smoke configuration') + console.log('') + console.log('Export for Controller:') + console.log(" export AUTH_MODE='embedded'") + console.log(` export CONTROLLER_PUBLIC_URL='${EMBEDDED_PUBLIC_URL}'`) + console.log(` export OIDC_CLIENT_ID='${EMBEDDED_CLIENT_ID}'`) + console.log(" export OIDC_BOOTSTRAP_ADMIN_EMAIL='admin@example.com'") + console.log(" export OIDC_BOOTSTRAP_ADMIN_PASSWORD='ChangeMeSecure123!'") + console.log('') + console.log('Optional for local HTTP smoke:') + console.log(" export AUTH_INSECURE_ALLOW_HTTP='true'") + console.log('') + console.log('Start Controller (dev mode):') + console.log(' npm run start-dev') + console.log('') + console.log('Login smoke:') + console.log(' curl -s -X POST http://localhost:51121/api/v3/user/login \\') + console.log(' -H "Content-Type: application/json" \\') + console.log(' -d \'{"email":"admin@example.com","password":"ChangeMeSecure123!"}\'') + console.log('') + console.log('Protected route smoke (replace ):') + console.log(' curl -H "Authorization: Bearer " http://localhost:51121/api/v3/user/profile') +} + +if (require.main === module) { + main() +} + +module.exports = { main } diff --git a/test/oidc/run-mock-provider.js b/test/oidc/run-mock-provider.js deleted file mode 100644 index b5cff8ad..00000000 --- a/test/oidc/run-mock-provider.js +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env node -/* - * Standalone mock OIDC issuer for local dev smoke runs. - * - * Usage: - * node test/oidc/run-mock-provider.js - * - * Then point Controller at the printed OIDC_* values and restart start-dev. - */ - -const { MockOidcProvider } = require('../support/mock-oidc-provider') -const { enableMockOidcTls } = require('../support/oidc-test-helpers') - -async function main () { - enableMockOidcTls() - const provider = new MockOidcProvider() - await provider.start() - - const env = provider.getEnv() - console.log('Mock OIDC provider listening') - console.log(` issuer: ${env.OIDC_ISSUER_URL}`) - console.log('') - console.log('Export for Controller:') - for (const [key, value] of Object.entries(env)) { - console.log(` export ${key}='${value}'`) - } - console.log('') - console.log('For local smoke, also run:') - console.log(" export NODE_TLS_REJECT_UNAUTHORIZED=0") - console.log('') - console.log('Press Ctrl+C to stop.') - - const shutdown = async () => { - await provider.stop() - process.exit(0) - } - - process.on('SIGINT', shutdown) - process.on('SIGTERM', shutdown) -} - -if (require.main === module) { - main().catch((error) => { - console.error(error) - process.exit(1) - }) -} - -module.exports = { main } diff --git a/test/src/config/embedded-oidc.test.js b/test/src/config/embedded-oidc.test.js new file mode 100644 index 00000000..c6603d75 --- /dev/null +++ b/test/src/config/embedded-oidc.test.js @@ -0,0 +1,156 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const express = require('express') +const http = require('http') + +const { + snapshotOidcEnv, + restoreOidcEnv, + applyOidcEnv, + reloadOidcModule +} = require('../../support/oidc-test-helpers') + +function requestJson (app, path, headers = {}) { + return new Promise((resolve, reject) => { + const server = app.listen(0, '127.0.0.1', () => { + const { port } = server.address() + http.get({ + hostname: '127.0.0.1', + port, + path, + headers + }, (res) => { + const chunks = [] + res.on('data', (chunk) => chunks.push(chunk)) + res.on('end', () => { + server.close() + resolve({ + status: res.statusCode, + body: JSON.parse(Buffer.concat(chunks).toString('utf8')) + }) + }) + }).on('error', (error) => { + server.close() + reject(error) + }) + }) + }) +} + +function reloadEmbeddedOidcModule () { + const embeddedPath = require.resolve('../../../src/config/embedded-oidc') + delete require.cache[embeddedPath] + return require('../../../src/config/embedded-oidc') +} + +function createStubDb () { + const storedKeys = [] + const storedClients = [] + + return { + AuthOidcKey: { + findAll: sinon.stub().resolves(storedKeys), + create: sinon.stub().callsFake(async (values) => { + storedKeys.push(values) + return values + }) + }, + AuthOidcClient: { + findOne: sinon.stub().callsFake(async ({ where }) => { + return storedClients.find((client) => client.clientId === where.clientId) || null + }), + create: sinon.stub().callsFake(async (values) => { + storedClients.push(values) + return values + }) + }, + AuthOidcProviderState: { + upsert: sinon.stub().resolves([{}, true]), + findOne: sinon.stub().resolves(null), + update: sinon.stub().resolves([1]), + destroy: sinon.stub().resolves(0) + }, + AuthPolicy: { + findByPk: sinon.stub().resolves({ + accessTokenTtlSeconds: 900, + refreshTokenTtlSeconds: 604800 + }) + }, + AuthUser: { + findByPk: sinon.stub().resolves(null) + } + } +} + +describe('Embedded OIDC issuer', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('publicUrl', () => 'http://controller.test') + + beforeEach(() => { + applyOidcEnv({ + AUTH_MODE: 'embedded', + CONTROLLER_PUBLIC_URL: $publicUrl + }) + reloadOidcModule() + }) + + afterEach(() => { + $sandbox.restore() + restoreOidcEnv($envSnapshot) + reloadEmbeddedOidcModule().resetEmbeddedIssuerForTests() + }) + + it('serves the discovery document at /oidc/.well-known/openid-configuration', async () => { + const embeddedOidc = reloadEmbeddedOidcModule() + const app = express() + const db = createStubDb() + + await embeddedOidc.initEmbeddedIssuer(app, { db }) + + const res = await requestJson(app, '/oidc/.well-known/openid-configuration', { + Host: 'controller.test' + }) + + expect(res.status).to.equal(200) + expect(res.body.issuer).to.equal(`${$publicUrl}/oidc`) + expect(res.body.jwks_uri).to.equal(`${$publicUrl}/oidc/jwks`) + expect(res.body.token_endpoint).to.equal(`${$publicUrl}/oidc/token`) + expect(res.body.revocation_endpoint).to.equal(`${$publicUrl}/oidc/revoke`) + expect(res.body.grant_types_supported).to.include('authorization_code') + }) + + it('persists a generated signing key when none exists', async () => { + const embeddedOidc = reloadEmbeddedOidcModule() + const app = express() + const db = createStubDb() + + await embeddedOidc.initEmbeddedIssuer(app, { db }) + + expect(db.AuthOidcKey.create).to.have.been.calledOnce + expect(db.AuthOidcKey.create.firstCall.args[0]).to.include({ + active: true + }) + expect(db.AuthOidcKey.create.firstCall.args[0].keyMaterialEncrypted).to.be.a('string') + }) + + it('registers the optional ecn-viewer public client when enabled', async () => { + applyOidcEnv({ + AUTH_MODE: 'embedded', + CONTROLLER_PUBLIC_URL: $publicUrl, + AUTH_VIEWER_CLIENT_ENABLED: 'true', + OIDC_VIEWER_CLIENT_ID: 'ecn-viewer' + }) + + const embeddedOidc = reloadEmbeddedOidcModule() + const app = express() + const db = createStubDb() + + await embeddedOidc.initEmbeddedIssuer(app, { db }) + + expect(db.AuthOidcClient.create).to.have.been.called + const createdClientIds = db.AuthOidcClient.create.getCalls().map((call) => call.args[0].clientId) + expect(createdClientIds).to.include('controller') + expect(createdClientIds).to.include('ecn-viewer') + }) +}) diff --git a/test/src/config/oidc.test.js b/test/src/config/oidc.test.js index 25352bf3..2500b3ff 100644 --- a/test/src/config/oidc.test.js +++ b/test/src/config/oidc.test.js @@ -2,13 +2,15 @@ const { expect } = require('chai') const sinon = require('sinon') const config = require('../../../src/config') -const { MockOidcProvider } = require('../../support/mock-oidc-provider') const { snapshotOidcEnv, - restoreOidcEnv, + applyEmbeddedEnv, + applyExternalEnv, + createEmbeddedAuthHarness, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') +const { applyOidcEnv, - enableMockOidcTls, - restoreMockOidcTls, reloadOidcModule, runMiddleware } = require('../../support/oidc-test-helpers') @@ -16,42 +18,57 @@ const { describe('OIDC config', () => { def('sandbox', () => sinon.createSandbox()) def('envSnapshot', () => snapshotOidcEnv()) - def('provider', () => new MockOidcProvider()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) - beforeEach(async () => { - enableMockOidcTls() - await $provider.start() + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) }) - afterEach(async () => { - $sandbox.restore() - restoreOidcEnv($envSnapshot) - restoreMockOidcTls() - await $provider.stop() + describe('getAuthMode()', () => { + it('defaults to embedded when AUTH_MODE is unset', () => { + applyOidcEnv({}) + const oidc = reloadOidcModule() + expect(oidc.getAuthMode()).to.equal('embedded') + }) + + it('throws for an invalid AUTH_MODE value', () => { + applyOidcEnv({ AUTH_MODE: 'keycloak' }) + const oidc = reloadOidcModule() + expect(() => oidc.getAuthMode()).to.throw('Invalid auth.mode') + }) }) describe('isAuthConfigured()', () => { - it('returns false when OIDC env vars are unset', () => { + it('returns false when embedded mode has no public URL', () => { applyOidcEnv({}) const oidc = reloadOidcModule() expect(oidc.isAuthConfigured()).to.equal(false) }) - it('returns true when issuer, client id, and secret are set', () => { - applyOidcEnv($provider.getEnv()) + it('returns true in embedded mode when CONTROLLER_PUBLIC_URL is set', () => { + applyEmbeddedEnv() + const oidc = reloadOidcModule() + expect(oidc.isAuthConfigured()).to.equal(true) + }) + + it('returns true in external mode when issuer, client id, and secret are set', () => { + applyExternalEnv() const oidc = reloadOidcModule() expect(oidc.isAuthConfigured()).to.equal(true) }) }) - describe('initOidc() dev mode', () => { - it('uses pass-through middleware when auth is not configured', async () => { + describe('initOidc() without auth config', () => { + it('initializes without bearer validation when auth is not configured in dev mode', async () => { applyOidcEnv({}) const oidc = reloadOidcModule() oidc.initOidc() const result = await runMiddleware(oidc.getOidcMiddleware(), { - headers: {} + headers: { + authorization: 'Bearer some-token' + } }) expect(result.nextCalled).to.equal(true) @@ -77,36 +94,41 @@ describe('OIDC config', () => { }) }) - describe('getOidcMiddleware() with mock issuer', () => { - beforeEach(() => { - applyOidcEnv($provider.getEnv()) + describe('getOidcMiddleware() with embedded issuer', () => { + beforeEach(async () => { + await $harness }) it('populates req.kauth for a valid bearer token', async () => { - const oidc = reloadOidcModule() - oidc.initOidc() - const token = await $provider.issueAccessToken({ - preferred_username: 'alice', - roles: ['SRE'] + const { store, modules } = await $harness + await store.seedUser({ + email: 'alice@example.com', + groupNames: ['sre'] }) - const result = await runMiddleware(oidc.getOidcMiddleware(), { + const loginResult = await modules.UserService.login({ + email: 'alice@example.com', + password: require('../../support/embedded-auth-harness').DEFAULT_TEST_PASSWORD + }, false) + + modules.oidc.initOidc() + const result = await runMiddleware(modules.oidc.getOidcMiddleware(), { headers: { - authorization: `Bearer ${token}` + authorization: `Bearer ${loginResult.accessToken}` } }) expect(result.nextCalled).to.equal(true) - expect(result.req.kauth.grant.access_token.token).to.equal(token) - expect(result.req.kauth.grant.access_token.content.preferred_username).to.equal('alice') - expect(result.req.kauth.grant.access_token.content.roles).to.deep.equal(['SRE']) + expect(result.req.kauth.grant.access_token.token).to.equal(loginResult.accessToken) + expect(result.req.kauth.grant.access_token.content.preferred_username).to.equal('alice@example.com') + expect(result.req.kauth.grant.access_token.content.groups).to.deep.equal(['sre']) }) it('leaves req.kauth unset for an invalid bearer token', async () => { - const oidc = reloadOidcModule() - oidc.initOidc() + const { modules } = await $harness + modules.oidc.initOidc() - const result = await runMiddleware(oidc.getOidcMiddleware(), { + const result = await runMiddleware(modules.oidc.getOidcMiddleware(), { headers: { authorization: 'Bearer not-a-valid-jwt' } @@ -117,10 +139,10 @@ describe('OIDC config', () => { }) it('passes through when Authorization header is missing', async () => { - const oidc = reloadOidcModule() - oidc.initOidc() + const { modules } = await $harness + modules.oidc.initOidc() - const result = await runMiddleware(oidc.getOidcMiddleware(), { + const result = await runMiddleware(modules.oidc.getOidcMiddleware(), { headers: {} }) @@ -129,10 +151,10 @@ describe('OIDC config', () => { }) it('skips OIDC validation for agent routes', async () => { - const oidc = reloadOidcModule() - oidc.initOidc() + const { modules } = await $harness + modules.oidc.initOidc() - const result = await runMiddleware(oidc.getOidcMiddleware(), { + const result = await runMiddleware(modules.oidc.getOidcMiddleware(), { path: '/api/v3/agent/status', headers: { authorization: 'Bearer not-an-oidc-token' diff --git a/test/src/lib/rbac/middleware-oidc.test.js b/test/src/lib/rbac/middleware-oidc.test.js index 2bbd151b..770ac591 100644 --- a/test/src/lib/rbac/middleware-oidc.test.js +++ b/test/src/lib/rbac/middleware-oidc.test.js @@ -3,13 +3,14 @@ const sinon = require('sinon') const authorizer = require('../../../../src/lib/rbac/authorizer') const rbacMiddleware = require('../../../../src/lib/rbac/middleware') -const { MockOidcProvider } = require('../../../support/mock-oidc-provider') const { snapshotOidcEnv, - restoreOidcEnv, + createEmbeddedAuthHarness, + teardownEmbeddedAuth, + EMBEDDED_CLIENT_ID +} = require('../../../support/embedded-auth-harness') +const { applyOidcEnv, - enableMockOidcTls, - restoreMockOidcTls, reloadOidcModule, runMiddleware } = require('../../../support/oidc-test-helpers') @@ -17,18 +18,15 @@ const { describe('RBAC middleware OIDC integration', () => { def('sandbox', () => sinon.createSandbox()) def('envSnapshot', () => snapshotOidcEnv()) - def('provider', () => new MockOidcProvider()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) beforeEach(async () => { - enableMockOidcTls() - await $provider.start() + await $harness }) - afterEach(async () => { + afterEach(() => { $sandbox.restore() - restoreOidcEnv($envSnapshot) - restoreMockOidcTls() - await $provider.stop() + teardownEmbeddedAuth($envSnapshot) }) describe('extractSubjects()', () => { @@ -53,7 +51,11 @@ describe('RBAC middleware OIDC integration', () => { }) it('extracts Keycloak-style resource_access roles for the configured client', () => { - applyOidcEnv($provider.getEnv()) + applyOidcEnv({ + AUTH_MODE: 'embedded', + CONTROLLER_PUBLIC_URL: 'https://controller.test', + OIDC_CLIENT_ID: EMBEDDED_CLIENT_ID + }) reloadOidcModule() const req = { @@ -63,7 +65,7 @@ describe('RBAC middleware OIDC integration', () => { content: { preferred_username: 'bob', resource_access: { - [$provider.clientId]: { + [EMBEDDED_CLIENT_ID]: { roles: ['Viewer'] } } @@ -127,19 +129,22 @@ describe('RBAC middleware OIDC integration', () => { expect($res.statusCode).to.equal(null) }) - it('authorizes catalog routes using subjects from OIDC bearer tokens', async () => { - applyOidcEnv($provider.getEnv()) - const oidc = reloadOidcModule() - oidc.initOidc() - - const token = await $provider.issueAccessToken({ - preferred_username: 'alice', - roles: ['SRE'] + it('authorizes catalog routes using subjects from embedded bearer tokens', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'alice@example.com', + groupNames: ['sre'] }) - const middlewareResult = await runMiddleware(oidc.getOidcMiddleware(), { + const loginResult = await modules.UserService.login({ + email: 'alice@example.com', + password: require('../../../support/embedded-auth-harness').DEFAULT_TEST_PASSWORD + }, false) + + modules.oidc.initOidc() + const middlewareResult = await runMiddleware(modules.oidc.getOidcMiddleware(), { headers: { - authorization: `Bearer ${token}` + authorization: `Bearer ${loginResult.accessToken}` } }) diff --git a/test/src/services/auth-integration.test.js b/test/src/services/auth-integration.test.js new file mode 100644 index 00000000..c9167ff5 --- /dev/null +++ b/test/src/services/auth-integration.test.js @@ -0,0 +1,246 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const { generateSecret, generateSync } = require('otplib') +const Errors = require('../../../src/helpers/errors') +const { + snapshotOidcEnv, + DEFAULT_TEST_PASSWORD, + EMBEDDED_PUBLIC_URL, + EMBEDDED_CLIENT_ID, + createEmbeddedAuthHarness, + applyExternalEnv, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') +const { verifyEmbeddedAccessToken } = require('../../support/embedded-auth-smoke') + +describe('Embedded auth integration', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + + beforeEach(async () => { + await $harness + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + describe('login and session', () => { + it('issues tokens for a viewer user without MFA', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const result = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + expect(result.accessToken).to.be.a('string').that.is.not.empty + expect(result.refreshToken).to.be.a('string').that.is.not.empty + }) + + it('issues tokens for admin users with MFA when totp is provided', async () => { + const { store, modules } = await $harness + const totpSecret = generateSecret() + await store.seedUser({ + email: 'admin@example.com', + groupNames: ['admin'], + mfaEnabled: true, + totpSecret + }) + + const code = generateSync({ secret: totpSecret }) + const result = await modules.UserService.login({ + email: 'admin@example.com', + password: DEFAULT_TEST_PASSWORD, + totp: code + }, false) + + expect(result.accessToken).to.be.a('string').that.is.not.empty + expect(result.refreshToken).to.be.a('string').that.is.not.empty + }) + + it('rejects admin login without totp when MFA is enabled', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'admin@example.com', + groupNames: ['admin'], + mfaEnabled: true + }) + + try { + await modules.UserService.login({ + email: 'admin@example.com', + password: DEFAULT_TEST_PASSWORD, + totp: '' + }, false) + expect.fail('expected login to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.InvalidCredentialsError) + } + }) + + it('allows bootstrap admin login without MFA', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'bootstrap@example.com', + groupNames: ['admin'], + isBootstrap: true + }) + + const result = await modules.UserService.login({ + email: 'bootstrap@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + expect(result.accessToken).to.be.a('string').that.is.not.empty + expect(result.refreshToken).to.be.a('string').that.is.not.empty + }) + + it('rejects non-bootstrap admin login when MFA is not enrolled', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'newadmin@example.com', + groupNames: ['admin'] + }) + + try { + await modules.UserService.login({ + email: 'newadmin@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + expect.fail('expected login to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.InvalidCredentialsError) + } + }) + + it('rotates refresh tokens', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const loginResult = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const refreshResult = await modules.UserService.refresh({ + refreshToken: loginResult.refreshToken + }, false) + + expect(refreshResult.accessToken).to.be.a('string').that.is.not.empty + expect(refreshResult.refreshToken).to.be.a('string').that.is.not.empty + expect(refreshResult.refreshToken).to.not.equal(loginResult.refreshToken) + }) + + it('validates issued access tokens via embedded JWKS', async () => { + const { store, modules, signing } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const loginResult = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const { payload } = await verifyEmbeddedAccessToken(loginResult.accessToken, { + issuer: `${EMBEDDED_PUBLIC_URL}/oidc`, + audience: EMBEDDED_CLIENT_ID, + privateJwk: signing.privateJwk + }) + + expect(payload.email).to.equal('viewer@example.com') + expect(payload.groups).to.deep.equal(['viewer']) + }) + }) + + describe('user admin', () => { + it('creates, lists, updates, and soft-deletes users', async () => { + const { modules } = await $harness + + const created = await modules.AuthUserService.createUser({ + email: 'new-user@example.com', + password: DEFAULT_TEST_PASSWORD, + groups: ['developer'] + }) + + expect(created.email).to.equal('new-user@example.com') + expect(created.groups).to.deep.equal(['developer']) + + const listed = await modules.AuthUserService.listUsers() + expect(listed.map((user) => user.email)).to.include('new-user@example.com') + + const updated = await modules.AuthUserService.updateUser(created.id, { + groups: ['sre'] + }) + expect(updated.groups).to.deep.equal(['sre']) + + await modules.AuthUserService.deleteUser(created.id) + const afterDelete = await modules.AuthUserService.listUsers() + expect(afterDelete.map((user) => user.email)).to.not.include('new-user@example.com') + }) + }) + + describe('migration export', () => { + it('exports users and groups for external migration', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const exportData = await modules.AuthMigrationService.exportMigrationData() + + expect(exportData.authMode).to.equal('embedded') + expect(exportData.users).to.have.length(1) + expect(exportData.users[0].email).to.equal('viewer@example.com') + expect(exportData.users[0].groups).to.deep.equal(['viewer']) + expect(exportData.groups.map((group) => group.name)).to.include.members(['admin', 'viewer']) + }) + }) + +}) + +describe('Embedded auth external mode', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('modules', () => require('../../support/embedded-auth-harness').reloadAuthModules()) + + beforeEach(() => { + applyExternalEnv() + require('../../support/embedded-auth-harness').reloadAuthModules() + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + it('returns 501 for embedded-only user admin APIs', () => { + try { + $modules.AuthUserService.ensureEmbeddedMode() + expect.fail('expected NotImplementedError') + } catch (error) { + expect(error).to.be.instanceOf(Errors.NotImplementedError) + } + }) + + it('returns 501 for migration export in external mode', async () => { + try { + await $modules.AuthMigrationService.exportMigrationData() + expect.fail('expected NotImplementedError') + } catch (error) { + expect(error).to.be.instanceOf(Errors.NotImplementedError) + } + }) +}) diff --git a/test/src/services/auth-login.test.js b/test/src/services/auth-login.test.js new file mode 100644 index 00000000..48998611 --- /dev/null +++ b/test/src/services/auth-login.test.js @@ -0,0 +1,265 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const { generateKeyPair, exportJWK } = require('jose') + +const AuthLoginService = require('../../../src/services/auth-login-service') +const AuthMfaService = require('../../../src/services/auth-mfa-service') +const AuthTokenService = require('../../../src/services/auth-token-service') +const AuthPolicyService = require('../../../src/services/auth-policy-service') +const AuthPasswordService = require('../../../src/services/auth-password-service') +const Errors = require('../../../src/helpers/errors') + +describe('Embedded auth login service', () => { + def('sandbox', () => sinon.createSandbox()) + + afterEach(() => { + $sandbox.restore() + }) + + describe('login()', () => { + it('returns tokens when credentials are valid and MFA is not required', async () => { + const user = { id: 'user-1', email: 'user@example.com', passwordHash: 'hash' } + $sandbox.stub(AuthMfaService, 'loadUserAuthContext').resolves({ + user, + groups: [{ name: 'viewer' }], + mfa: null, + groupNames: ['viewer'] + }) + $sandbox.stub(AuthPolicyService, 'getPolicy').resolves(AuthPolicyService.DEFAULT_POLICY) + $sandbox.stub(AuthPolicyService, 'isAccountLocked').returns(false) + $sandbox.stub(AuthPasswordService, 'verifyPassword').resolves(true) + $sandbox.stub(AuthPolicyService, 'resetFailedLogin').resolves(user) + $sandbox.stub(AuthTokenService, 'issueTokenPair').resolves({ + accessToken: 'access-token', + refreshToken: 'refresh-token' + }) + + const result = await AuthLoginService.login({ + email: 'user@example.com', + password: 'correct-password' + }) + + expect(result).to.deep.equal({ + accessToken: 'access-token', + refreshToken: 'refresh-token' + }) + }) + + it('returns tokens for admin with MFA when totp is valid', async () => { + const user = { id: 'admin-1', email: 'admin@example.com', passwordHash: 'hash', isBootstrap: false } + $sandbox.stub(AuthMfaService, 'loadUserAuthContext').resolves({ + user, + groups: [{ name: 'admin' }], + mfa: { enabled: true }, + groupNames: ['admin'] + }) + $sandbox.stub(AuthPolicyService, 'getPolicy').resolves(AuthPolicyService.DEFAULT_POLICY) + $sandbox.stub(AuthPolicyService, 'isAccountLocked').returns(false) + $sandbox.stub(AuthPasswordService, 'verifyPassword').resolves(true) + $sandbox.stub(AuthMfaService, 'verifyMfaCode').resolves(true) + $sandbox.stub(AuthPolicyService, 'resetFailedLogin').resolves(user) + $sandbox.stub(AuthTokenService, 'issueTokenPair').resolves({ + accessToken: 'access-token', + refreshToken: 'refresh-token' + }) + + const result = await AuthLoginService.login({ + email: 'admin@example.com', + password: 'correct-password', + totp: '123456' + }) + + expect(result.accessToken).to.equal('access-token') + expect(AuthMfaService.verifyMfaCode).to.have.been.calledOnceWith('admin-1', '123456') + }) + + it('throws InvalidCredentialsError for admin with MFA when totp is missing', async () => { + const user = { id: 'admin-1', email: 'admin@example.com', passwordHash: 'hash', isBootstrap: false } + $sandbox.stub(AuthMfaService, 'loadUserAuthContext').resolves({ + user, + groups: [{ name: 'admin' }], + mfa: { enabled: true }, + groupNames: ['admin'] + }) + $sandbox.stub(AuthPolicyService, 'getPolicy').resolves(AuthPolicyService.DEFAULT_POLICY) + $sandbox.stub(AuthPolicyService, 'isAccountLocked').returns(false) + $sandbox.stub(AuthPasswordService, 'verifyPassword').resolves(true) + + try { + await AuthLoginService.login({ + email: 'admin@example.com', + password: 'correct-password', + totp: '' + }) + expect.fail('expected login to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.InvalidCredentialsError) + } + }) + + it('throws InvalidCredentialsError for non-bootstrap admin without MFA enrolled', async () => { + const user = { id: 'admin-2', email: 'admin2@example.com', passwordHash: 'hash', isBootstrap: false } + $sandbox.stub(AuthMfaService, 'loadUserAuthContext').resolves({ + user, + groups: [{ name: 'admin' }], + mfa: null, + groupNames: ['admin'] + }) + $sandbox.stub(AuthPolicyService, 'getPolicy').resolves(AuthPolicyService.DEFAULT_POLICY) + $sandbox.stub(AuthPolicyService, 'isAccountLocked').returns(false) + $sandbox.stub(AuthPasswordService, 'verifyPassword').resolves(true) + + try { + await AuthLoginService.login({ + email: 'admin2@example.com', + password: 'correct-password' + }) + expect.fail('expected login to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.InvalidCredentialsError) + } + }) + + it('returns tokens for bootstrap admin without MFA', async () => { + const user = { id: 'bootstrap-1', email: 'bootstrap@example.com', passwordHash: 'hash', isBootstrap: true } + $sandbox.stub(AuthMfaService, 'loadUserAuthContext').resolves({ + user, + groups: [{ name: 'admin' }], + mfa: null, + groupNames: ['admin'] + }) + $sandbox.stub(AuthPolicyService, 'getPolicy').resolves(AuthPolicyService.DEFAULT_POLICY) + $sandbox.stub(AuthPolicyService, 'isAccountLocked').returns(false) + $sandbox.stub(AuthPasswordService, 'verifyPassword').resolves(true) + $sandbox.stub(AuthPolicyService, 'resetFailedLogin').resolves(user) + $sandbox.stub(AuthTokenService, 'issueTokenPair').resolves({ + accessToken: 'access-token', + refreshToken: 'refresh-token' + }) + + const result = await AuthLoginService.login({ + email: 'bootstrap@example.com', + password: 'correct-password' + }) + + expect(result.accessToken).to.equal('access-token') + }) + + it('throws InvalidCredentialsError for unknown users', async () => { + $sandbox.stub(AuthMfaService, 'loadUserAuthContext').resolves(null) + + try { + await AuthLoginService.login({ + email: 'missing@example.com', + password: 'password' + }) + expect.fail('expected login to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.InvalidCredentialsError) + } + }) + }) +}) + +describe('Auth password service', () => { + it('hashes and verifies passwords with Argon2id', async () => { + const hash = await AuthPasswordService.hashPassword('SecurePass123') + expect(await AuthPasswordService.verifyPassword('SecurePass123', hash)).to.equal(true) + expect(await AuthPasswordService.verifyPassword('wrong', hash)).to.equal(false) + }) + + it('validates password complexity against policy defaults', () => { + expect(() => AuthPasswordService.validatePasswordComplexity('short', AuthPolicyService.DEFAULT_POLICY)) + .to.throw(Errors.ValidationError) + }) +}) + +describe('Embedded OIDC local JWKS validation', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => require('../../support/oidc-test-helpers').snapshotOidcEnv()) + + afterEach(() => { + $sandbox.restore() + require('../../support/oidc-test-helpers').restoreOidcEnv($envSnapshot) + require('../../../src/config/oidc').resetDiscoveryForTests() + require('../../../src/config/auth-jwks').resetSigningMaterialCacheForTests() + }) + + it('validates embedded access tokens via local JWKS', async () => { + const { applyOidcEnv, reloadOidcModule, runMiddleware } = require('../../support/oidc-test-helpers') + const AuthTokenService = require('../../../src/services/auth-token-service') + const AuthJwks = require('../../../src/config/auth-jwks') + + applyOidcEnv({ + AUTH_MODE: 'embedded', + CONTROLLER_PUBLIC_URL: 'https://controller.test' + }) + + const { publicKey, privateKey } = await generateKeyPair('RS256') + const privateJwk = await exportJWK(privateKey) + privateJwk.kid = 'test-kid' + privateJwk.alg = 'RS256' + privateJwk.use = 'sig' + + $sandbox.stub(AuthJwks, 'getActiveSigningMaterial').resolves({ + kid: 'test-kid', + privateJwk, + signingKey: privateKey + }) + + const user = { id: 'user-uuid', email: 'admin@example.com' } + const accessToken = await AuthTokenService.issueAccessToken(user, ['admin'], AuthPolicyService.DEFAULT_POLICY) + + const oidc = reloadOidcModule() + oidc.initOidc() + + const result = await runMiddleware(oidc.getOidcMiddleware(), { + path: '/api/v3/application', + headers: { + authorization: `Bearer ${accessToken}` + } + }) + + expect(result.nextCalled).to.equal(true) + expect(result.req.kauth.grant.access_token.content.sub).to.equal('user-uuid') + expect(result.req.kauth.grant.access_token.content.groups).to.deep.equal(['admin']) + }) +}) + +describe('RBAC middleware without bearer token', () => { + def('sandbox', () => sinon.createSandbox()) + def('callback', () => $sandbox.spy()) + + afterEach(() => { + $sandbox.restore() + }) + + it('returns 401 when no authentication information is present', async () => { + const authorizer = require('../../../src/lib/rbac/authorizer') + $sandbox.stub(authorizer, 'authorize').resolves(true) + + const rbacMiddleware = require('../../../src/lib/rbac/middleware') + const req = { + method: 'GET', + path: '/api/v3/application', + kauth: undefined + } + const res = { + statusCode: null, + body: null, + status (code) { + this.statusCode = code + return this + }, + json (payload) { + this.body = payload + return this + } + } + + await rbacMiddleware.protect()(req, res, $callback) + + expect(res.statusCode).to.equal(401) + expect($callback).to.not.have.been.called + }) +}) diff --git a/test/src/services/user-service-oidc.test.js b/test/src/services/user-service-oidc.test.js index 0e3f5b23..3089a3cc 100644 --- a/test/src/services/user-service-oidc.test.js +++ b/test/src/services/user-service-oidc.test.js @@ -1,48 +1,41 @@ const { expect } = require('chai') const sinon = require('sinon') -const config = require('../../../src/config') -const { MockOidcProvider } = require('../../support/mock-oidc-provider') const { snapshotOidcEnv, - restoreOidcEnv, - applyOidcEnv, - enableMockOidcTls, - restoreMockOidcTls, - reloadOidcModule -} = require('../../support/oidc-test-helpers') - -function reloadUserServiceModule () { - const userServicePath = require.resolve('../../../src/services/user-service') - delete require.cache[userServicePath] - return require('../../../src/services/user-service') -} + DEFAULT_TEST_PASSWORD, + createEmbeddedAuthHarness, + teardownEmbeddedAuth, + applyEmbeddedEnv, + reloadAuthModules +} = require('../../support/embedded-auth-harness') +const { applyOidcEnv, reloadOidcModule } = require('../../support/oidc-test-helpers') describe('User service OIDC', () => { def('sandbox', () => sinon.createSandbox()) def('envSnapshot', () => snapshotOidcEnv()) - def('provider', () => new MockOidcProvider()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) beforeEach(async () => { - enableMockOidcTls() - await $provider.start() - applyOidcEnv($provider.getEnv()) - reloadOidcModule() + await $harness }) - afterEach(async () => { + afterEach(() => { $sandbox.restore() - restoreOidcEnv($envSnapshot) - restoreMockOidcTls() - await $provider.stop() + teardownEmbeddedAuth($envSnapshot) }) - describe('login()', () => { - it('returns access and refresh tokens from the issuer token endpoint', async () => { - const UserService = reloadUserServiceModule() - const result = await UserService.login({ - email: $provider.username, - password: $provider.password + describe('embedded login()', () => { + it('returns access and refresh tokens for valid credentials', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const result = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD }, false) expect(result.accessToken).to.be.a('string').that.is.not.empty @@ -50,10 +43,15 @@ describe('User service OIDC', () => { }) it('throws InvalidCredentialsError for bad password', async () => { - const UserService = reloadUserServiceModule() + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + try { - await UserService.login({ - email: $provider.username, + await modules.UserService.login({ + email: 'viewer@example.com', password: 'wrong-password' }, false) expect.fail('expected login to fail') @@ -63,15 +61,20 @@ describe('User service OIDC', () => { }) }) - describe('refresh()', () => { + describe('embedded refresh()', () => { it('returns a new access token for a valid refresh token', async () => { - const UserService = reloadUserServiceModule() - const loginResult = await UserService.login({ - email: $provider.username, - password: $provider.password + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const loginResult = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD }, false) - const refreshResult = await UserService.refresh({ + const refreshResult = await modules.UserService.refresh({ refreshToken: loginResult.refreshToken }, false) @@ -80,34 +83,45 @@ describe('User service OIDC', () => { }) }) - describe('profile()', () => { - it('returns userinfo claims for a valid bearer token', async () => { - const UserService = reloadUserServiceModule() - const loginResult = await UserService.login({ - email: $provider.username, - password: $provider.password + describe('embedded profile()', () => { + it('returns JWT claims for a valid bearer token', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const loginResult = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD }, false) - const profile = await UserService.profile({ + const profile = await modules.UserService.profile({ headers: { authorization: `Bearer ${loginResult.accessToken}` } }, false) - expect(profile.preferred_username).to.equal($provider.username) - expect(profile.email).to.equal(`${$provider.username}@example.com`) + expect(profile.preferred_username).to.equal('viewer@example.com') + expect(profile.email).to.equal('viewer@example.com') + expect(profile.groups).to.deep.equal(['viewer']) }) }) - describe('logout()', () => { - it('returns success after best-effort token revocation', async () => { - const UserService = reloadUserServiceModule() - const loginResult = await UserService.login({ - email: $provider.username, - password: $provider.password + describe('embedded logout()', () => { + it('returns success after revoking refresh tokens', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const loginResult = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD }, false) - const result = await UserService.logout({ + const result = await modules.UserService.logout({ headers: { authorization: `Bearer ${loginResult.accessToken}` } @@ -117,29 +131,23 @@ describe('User service OIDC', () => { }) }) - describe('dev mode without auth config', () => { + describe('without auth config', () => { beforeEach(() => { applyOidcEnv({}) reloadOidcModule() }) - it('returns mock tokens when auth is not configured', async () => { - const originalGet = config.get.bind(config) - $sandbox.stub(config, 'get').callsFake((key, defaultValue) => { - if (key === 'server.devMode') { - return true - } - return originalGet(key, defaultValue) - }) - - const UserService = reloadUserServiceModule() - const result = await UserService.login({ - email: 'dev@example.com', - password: 'password' - }, false) - - expect(result.accessToken).to.equal('mock-access-token') - expect(result.refreshToken).to.equal('mock-refresh-token') + it('throws when auth is not configured', async () => { + const { UserService } = reloadAuthModules() + try { + await UserService.login({ + email: 'dev@example.com', + password: 'password' + }, false) + expect.fail('expected login to fail') + } catch (error) { + expect(error.message).to.include('Auth is not configured') + } }) }) }) diff --git a/test/src/support/embedded-auth-smoke.test.js b/test/src/support/embedded-auth-smoke.test.js new file mode 100644 index 00000000..c47c7eaa --- /dev/null +++ b/test/src/support/embedded-auth-smoke.test.js @@ -0,0 +1,52 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const { + snapshotOidcEnv, + DEFAULT_TEST_PASSWORD, + EMBEDDED_PUBLIC_URL, + EMBEDDED_CLIENT_ID, + createEmbeddedAuthHarness, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') +const { verifyEmbeddedAccessToken } = require('../../support/embedded-auth-smoke') + +describe('Embedded auth smoke', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + + beforeEach(async () => { + await $harness + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + it('issues RS256 access tokens validated by embedded JWKS', async () => { + const { store, modules, signing } = await $harness + await store.seedUser({ + email: 'smoke-user@example.com', + groupNames: ['sre'] + }) + + const loginResult = await modules.UserService.login({ + email: 'smoke-user@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const { token, payload } = await verifyEmbeddedAccessToken(loginResult.accessToken, { + issuer: `${EMBEDDED_PUBLIC_URL}/oidc`, + audience: EMBEDDED_CLIENT_ID, + privateJwk: signing.privateJwk + }) + + expect(token.split('.')).to.have.length(3) + expect(payload.preferred_username).to.equal('smoke-user@example.com') + expect(payload.groups).to.deep.equal(['sre']) + expect(payload.iss).to.equal(`${EMBEDDED_PUBLIC_URL}/oidc`) + expect(payload.aud).to.equal(EMBEDDED_CLIENT_ID) + }) +}) diff --git a/test/src/support/mock-oidc-smoke.test.js b/test/src/support/mock-oidc-smoke.test.js deleted file mode 100644 index 4d62a713..00000000 --- a/test/src/support/mock-oidc-smoke.test.js +++ /dev/null @@ -1,35 +0,0 @@ -const { expect } = require('chai') - -const { MockOidcProvider } = require('../../support/mock-oidc-provider') -const { - enableMockOidcTls, - restoreMockOidcTls -} = require('../../support/oidc-test-helpers') -const { verifyMockAccessToken } = require('../../support/oidc-smoke') - -describe('Mock OIDC provider smoke', () => { - def('provider', () => new MockOidcProvider()) - - beforeEach(async () => { - enableMockOidcTls() - await $provider.start() - }) - - afterEach(async () => { - restoreMockOidcTls() - await $provider.stop() - }) - - it('serves discovery metadata and validates RS256 access tokens', async () => { - const { token, payload } = await verifyMockAccessToken($provider, { - preferred_username: 'smoke-user', - roles: ['SRE'] - }) - - expect(token.split('.')).to.have.length(3) - expect(payload.preferred_username).to.equal('smoke-user') - expect(payload.roles).to.deep.equal(['SRE']) - expect(payload.iss).to.equal($provider.issuer) - expect(payload.aud).to.equal($provider.clientId) - }) -}) diff --git a/test/support/embedded-auth-harness.js b/test/support/embedded-auth-harness.js new file mode 100644 index 00000000..8168df91 --- /dev/null +++ b/test/support/embedded-auth-harness.js @@ -0,0 +1,414 @@ +const crypto = require('crypto') +const { generateKeyPair, exportJWK, importJWK } = require('jose') +const { Op } = require('sequelize') +const { generateSecret } = require('otplib') + +const AuthPasswordService = require('../../src/services/auth-password-service') +const secretHelper = require('../../src/helpers/secret-helper') +const { + snapshotOidcEnv, + restoreOidcEnv, + applyOidcEnv, + reloadOidcModule +} = require('./oidc-test-helpers') + +const EMBEDDED_PUBLIC_URL = 'https://controller.test' +const EMBEDDED_CLIENT_ID = 'controller' +const DEFAULT_TEST_PASSWORD = 'SecurePass123!' + +function createRecord (data) { + const record = { + ...data, + dataValues: { ...data }, + createdAt: data.createdAt || new Date(), + updatedAt: data.updatedAt || new Date() + } + + record.update = async function (fields) { + Object.assign(this, fields) + Object.assign(this.dataValues, fields) + this.updatedAt = new Date() + return this + } + + record.reload = async function () { + return this + } + + record.destroy = async function () { + record._deleted = true + return undefined + } + + record.get = function ({ plain } = {}) { + if (plain) { + const copy = { ...this } + delete copy.dataValues + delete copy.update + delete copy.reload + delete copy.destroy + delete copy.get + return copy + } + return this + } + + return record +} + +function getDefaultPolicy () { + return require('../../src/services/auth-policy-service').DEFAULT_POLICY +} + +function stubModelMethod (db, modelName, methodName, sandbox, impl) { + if (!db[modelName]) { + db[modelName] = {} + } + db[modelName][methodName] = sandbox.stub().callsFake(impl) +} + +function createEmbeddedAuthStore () { + const groups = new Map([ + ['admin', createRecord({ id: 'grp-admin', name: 'admin', isSystem: true })], + ['sre', createRecord({ id: 'grp-sre', name: 'sre', isSystem: true })], + ['developer', createRecord({ id: 'grp-developer', name: 'developer', isSystem: true })], + ['viewer', createRecord({ id: 'grp-viewer', name: 'viewer', isSystem: true })] + ]) + + const users = new Map() + const userGroups = new Map() + const mfaByUserId = new Map() + const refreshTokens = new Map() + const policy = createRecord({ id: 1, ...getDefaultPolicy() }) + + function getUserGroups (userId) { + const groupIds = userGroups.get(userId) || [] + return groupIds + .map((groupId) => [...groups.values()].find((group) => group.id === groupId)) + .filter(Boolean) + } + + function attachUserIncludes (user, include = []) { + for (const item of include || []) { + if (item.as === 'groups') { + user.groups = getUserGroups(user.id) + } + if (item.as === 'mfa') { + user.mfa = mfaByUserId.get(user.id) || null + } + } + return user + } + + async function seedUser ({ + email, + password = DEFAULT_TEST_PASSWORD, + groupNames = ['viewer'], + mfaEnabled = false, + totpSecret = null, + isBootstrap = false + }) { + const normalizedEmail = String(email).trim().toLowerCase() + const userId = crypto.randomUUID() + const passwordHash = await AuthPasswordService.hashPassword(password) + const user = createRecord({ + id: userId, + email: normalizedEmail, + passwordHash, + mustChangePassword: false, + isBootstrap, + failedAttempts: 0, + lockedUntil: null, + deletedAt: null, + passwordHistoryHashes: null + }) + + users.set(userId, user) + userGroups.set(userId, groupNames.map((name) => groups.get(name).id)) + + if (mfaEnabled) { + const secret = totpSecret || generateSecret() + const totpSecretEncrypted = await secretHelper.encryptSecret( + { secret }, + `auth-mfa-${userId}`, + 'auth-mfa' + ) + mfaByUserId.set(userId, createRecord({ + userId, + enabled: true, + totpSecretEncrypted, + recoveryCodesHash: null + })) + return { user, password, totpSecret: secret } + } + + return { user, password } + } + + return { + groups, + users, + userGroups, + mfaByUserId, + refreshTokens, + policy, + seedUser, + getUserGroups, + attachUserIncludes + } +} + +function createNoopTransaction () { + return { + commit: async () => {}, + rollback: async () => {}, + LOCK: { UPDATE: 'UPDATE' } + } +} + +function installEmbeddedAuthStore (sandbox, store) { + const db = require('../../src/data/models') + + db.sequelize = { + transaction: sandbox.stub().callsFake(async () => createNoopTransaction()) + } + + stubModelMethod(db, 'AuthPolicy', 'findByPk', sandbox, async () => store.policy) + + stubModelMethod(db, 'AuthGroup', 'findAll', sandbox, async ({ where } = {}) => { + const names = where && where.name && (where.name[Op.in] || where.name.in) + ? (where.name[Op.in] || where.name.in) + : [...store.groups.keys()] + return names + .map((name) => store.groups.get(String(name).toLowerCase())) + .filter(Boolean) + }) + + stubModelMethod(db, 'AuthGroup', 'findOne', sandbox, async ({ where } = {}) => { + return store.groups.get(where.name) || null + }) + + stubModelMethod(db, 'AuthGroup', 'findOrCreate', sandbox, async ({ where, defaults }) => { + const existing = store.groups.get(where.name) + if (existing) { + return [existing, false] + } + const created = createRecord({ id: crypto.randomUUID(), ...defaults }) + store.groups.set(where.name, created) + return [created, true] + }) + + stubModelMethod(db, 'AuthUser', 'findOne', sandbox, async ({ where, include } = {}) => { + let user = null + if (where.id) { + user = store.users.get(where.id) + } else if (where.email) { + user = [...store.users.values()].find((row) => row.email === where.email) + } + + if (!user) { + return null + } + if (where.deletedAt === null && user.deletedAt) { + return null + } + return store.attachUserIncludes(user, include || []) + }) + + stubModelMethod(db, 'AuthUser', 'findByPk', sandbox, async (userId, { include } = {}) => { + const user = store.users.get(userId) + if (!user) { + return null + } + return store.attachUserIncludes(user, include || []) + }) + + stubModelMethod(db, 'AuthUser', 'findAll', sandbox, async ({ where, include, order } = {}) => { + let rows = [...store.users.values()] + if (where && where.deletedAt === null) { + rows = rows.filter((row) => !row.deletedAt) + } + if (order) { + rows = rows.sort((a, b) => a.email.localeCompare(b.email)) + } + return rows.map((row) => store.attachUserIncludes(row, include || [])) + }) + + stubModelMethod(db, 'AuthUser', 'create', sandbox, async (values) => { + const user = createRecord({ + ...values, + failedAttempts: 0, + lockedUntil: null, + deletedAt: null, + passwordHistoryHashes: null, + mustChangePassword: values.mustChangePassword || false, + isBootstrap: values.isBootstrap || false + }) + store.users.set(user.id, user) + return user + }) + + stubModelMethod(db, 'AuthUserGroup', 'create', sandbox, async ({ userId, groupId }) => { + const links = store.userGroups.get(userId) || [] + links.push(groupId) + store.userGroups.set(userId, links) + return { userId, groupId } + }) + + stubModelMethod(db, 'AuthUserGroup', 'destroy', sandbox, async ({ where }) => { + if (where.userId) { + store.userGroups.set(where.userId, []) + } + return 1 + }) + + stubModelMethod(db, 'AuthMfa', 'findOne', sandbox, async ({ where } = {}) => { + return store.mfaByUserId.get(where.userId) || null + }) + + stubModelMethod(db, 'AuthMfa', 'create', sandbox, async (values) => { + const record = createRecord(values) + store.mfaByUserId.set(values.userId, record) + return record + }) + + stubModelMethod(db, 'AuthRefreshToken', 'create', sandbox, async (values) => { + const row = createRecord(values) + store.refreshTokens.set(values.tokenHash, row) + return row + }) + + stubModelMethod(db, 'AuthRefreshToken', 'findOne', sandbox, async ({ where } = {}) => { + return store.refreshTokens.get(where.tokenHash) || null + }) + + stubModelMethod(db, 'AuthRefreshToken', 'update', sandbox, async (values, { where } = {}) => { + for (const row of store.refreshTokens.values()) { + const matchesFamily = !where.familyId || row.familyId === where.familyId + const matchesUser = !where.userId || row.userId === where.userId + const matchesRevoked = where.revoked === undefined || row.revoked === where.revoked + if (matchesFamily && matchesUser && matchesRevoked) { + Object.assign(row, values) + } + } + return [1] + }) +} + +async function installEmbeddedSigningKey (sandbox) { + const AuthJwks = require('../../src/config/auth-jwks') + const { publicKey, privateKey } = await generateKeyPair('RS256') + const privateJwk = await exportJWK(privateKey) + privateJwk.kid = 'embedded-test-kid' + privateJwk.alg = 'RS256' + privateJwk.use = 'sig' + + sandbox.stub(AuthJwks, 'getActiveSigningMaterial').callsFake(async () => ({ + kid: privateJwk.kid, + privateJwk, + signingKey: privateKey + })) + + return { privateKey, privateJwk, publicKey } +} + +function applyEmbeddedEnv (overrides = {}) { + applyOidcEnv({ + AUTH_MODE: 'embedded', + CONTROLLER_PUBLIC_URL: EMBEDDED_PUBLIC_URL, + OIDC_CLIENT_ID: EMBEDDED_CLIENT_ID, + ...overrides + }) +} + +function applyExternalEnv (overrides = {}) { + applyOidcEnv({ + AUTH_MODE: 'external', + CONTROLLER_PUBLIC_URL: EMBEDDED_PUBLIC_URL, + OIDC_ISSUER_URL: 'https://idp.example.com/realms/test', + OIDC_CLIENT_ID: EMBEDDED_CLIENT_ID, + OIDC_CLIENT_SECRET: 'external-test-secret', + ...overrides + }) +} + +function reloadAuthModules ({ keepJwks = false } = {}) { + const modules = [ + '../../src/services/user-service', + '../../src/services/auth-login-service', + '../../src/services/auth-user-service', + '../../src/services/auth-migration-service', + '../../src/services/auth-token-service', + '../../src/config/oidc' + ] + + if (!keepJwks) { + modules.push('../../src/config/auth-jwks') + } + + for (const modulePath of modules) { + const resolved = require.resolve(modulePath) + delete require.cache[resolved] + } + + return { + UserService: require('../../src/services/user-service'), + AuthLoginService: require('../../src/services/auth-login-service'), + AuthUserService: require('../../src/services/auth-user-service'), + AuthMigrationService: require('../../src/services/auth-migration-service'), + oidc: reloadOidcModule() + } +} + +function resetEmbeddedAuthCaches () { + require('../../src/config/oidc').resetDiscoveryForTests() + require('../../src/config/auth-jwks').resetSigningMaterialCacheForTests() +} + +async function createEmbeddedAuthHarness (sandbox, options = {}) { + const store = createEmbeddedAuthStore() + installEmbeddedAuthStore(sandbox, store) + + applyEmbeddedEnv(options.env || {}) + resetEmbeddedAuthCaches() + + const signing = await installEmbeddedSigningKey(sandbox) + const modules = reloadAuthModules({ keepJwks: true }) + + return { + store, + signing, + modules, + async seedViewerUser (email = 'viewer@example.com') { + return store.seedUser({ email, groupNames: ['viewer'] }) + }, + async seedAdminUser (email = 'admin@example.com', { mfaEnabled = false, totpSecret } = {}) { + return store.seedUser({ + email, + groupNames: ['admin'], + mfaEnabled, + totpSecret + }) + } + } +} + +function teardownEmbeddedAuth (envSnapshot) { + resetEmbeddedAuthCaches() + restoreOidcEnv(envSnapshot) +} + +module.exports = { + EMBEDDED_PUBLIC_URL, + EMBEDDED_CLIENT_ID, + DEFAULT_TEST_PASSWORD, + snapshotOidcEnv, + applyEmbeddedEnv, + applyExternalEnv, + createEmbeddedAuthStore, + createEmbeddedAuthHarness, + installEmbeddedSigningKey, + reloadAuthModules, + resetEmbeddedAuthCaches, + teardownEmbeddedAuth +} diff --git a/test/support/embedded-auth-smoke.js b/test/support/embedded-auth-smoke.js new file mode 100644 index 00000000..5ccdfedb --- /dev/null +++ b/test/support/embedded-auth-smoke.js @@ -0,0 +1,32 @@ +const { createLocalJWKSet, jwtVerify } = require('jose') +const { getPublicJwk } = require('../../src/config/auth-jwks') + +/** + * Verify an embedded-mode access token against local JWKS material (no remote issuer). + */ +async function verifyEmbeddedAccessToken (accessToken, { issuer, audience, privateJwk }) { + const jwks = createLocalJWKSet({ keys: [getPublicJwk(privateJwk)] }) + const verifyOptions = { issuer } + if (audience) { + verifyOptions.audience = audience + } + + const { payload } = await jwtVerify(accessToken, jwks, verifyOptions) + return { token: accessToken, payload } +} + +function buildKauthGrant (payload, token) { + return { + grant: { + access_token: { + token, + content: payload + } + } + } +} + +module.exports = { + verifyEmbeddedAccessToken, + buildKauthGrant +} diff --git a/test/support/mock-oidc-provider.js b/test/support/mock-oidc-provider.js deleted file mode 100644 index 9c845d89..00000000 --- a/test/support/mock-oidc-provider.js +++ /dev/null @@ -1,262 +0,0 @@ -const https = require('https') -const { URL } = require('url') -const forge = require('node-forge') -const { generateKeyPair, exportJWK, SignJWT, calculateJwkThumbprint, decodeJwt } = require('jose') - -function createSelfSignedTlsCredentials () { - const keys = forge.pki.rsa.generateKeyPair(2048) - const certificate = forge.pki.createCertificate() - - certificate.publicKey = keys.publicKey - certificate.serialNumber = '01' - certificate.validity.notBefore = new Date() - certificate.validity.notAfter = new Date() - certificate.validity.notAfter.setFullYear(certificate.validity.notBefore.getFullYear() + 1) - - const attributes = [{ name: 'commonName', value: 'mock-oidc' }] - certificate.setSubject(attributes) - certificate.setIssuer(attributes) - certificate.setExtensions([{ - name: 'subjectAltName', - altNames: [{ type: 7, ip: '127.0.0.1' }] - }]) - certificate.sign(keys.privateKey, forge.md.sha256.create()) - - return { - key: forge.pki.privateKeyToPem(keys.privateKey), - cert: forge.pki.certificateToPem(certificate) - } -} - -/** - * Minimal OIDC issuer for unit tests and local dev smoke runs. - * Serves discovery, JWKS, token, userinfo, and revocation over HTTPS. - */ -class MockOidcProvider { - constructor (options = {}) { - this.clientId = options.clientId || 'controller-test-client' - this.clientSecret = options.clientSecret || 'test-client-secret' - this.username = options.username || 'test-user' - this.password = options.password || 'test-password' - this.port = options.port || 0 - this.server = null - this.baseUrl = null - this.issuer = null - this.privateKey = null - this.publicJwk = null - this.kid = null - this.tls = createSelfSignedTlsCredentials() - this.refreshTokens = new Map() - } - - async start () { - const { publicKey, privateKey } = await generateKeyPair('RS256') - this.privateKey = privateKey - this.publicJwk = await exportJWK(publicKey) - this.kid = await calculateJwkThumbprint(this.publicJwk) - this.publicJwk.kid = this.kid - this.publicJwk.alg = 'RS256' - this.publicJwk.use = 'sig' - - return new Promise((resolve, reject) => { - this.server = https.createServer(this.tls, (req, res) => { - this.handleRequest(req, res).catch((error) => { - res.statusCode = 500 - res.end(error.message) - }) - }) - this.server.on('error', reject) - this.server.listen(this.port, '127.0.0.1', () => { - const address = this.server.address() - this.baseUrl = `https://127.0.0.1:${address.port}` - this.issuer = this.baseUrl - resolve(this) - }) - }) - } - - async readBody (req) { - const chunks = [] - for await (const chunk of req) { - chunks.push(chunk) - } - return Buffer.concat(chunks).toString('utf8') - } - - async handleRequest (req, res) { - const url = new URL(req.url, this.baseUrl) - const pathname = url.pathname - - if (pathname === '/.well-known/openid-configuration') { - return this.sendJson(res, { - issuer: this.issuer, - jwks_uri: `${this.baseUrl}/jwks`, - token_endpoint: `${this.baseUrl}/token`, - userinfo_endpoint: `${this.baseUrl}/userinfo`, - revocation_endpoint: `${this.baseUrl}/revoke`, - authorization_endpoint: `${this.baseUrl}/authorize`, - response_types_supported: ['code'], - subject_types_supported: ['public'], - id_token_signing_alg_values_supported: ['RS256'] - }) - } - - if (pathname === '/jwks') { - return this.sendJson(res, { keys: [this.publicJwk] }) - } - - if (pathname === '/token' && req.method === 'POST') { - return this.handleTokenRequest(req, res) - } - - if (pathname === '/userinfo' && req.method === 'GET') { - return this.handleUserInfoRequest(req, res) - } - - if (pathname === '/revoke' && req.method === 'POST') { - res.statusCode = 200 - return res.end() - } - - res.statusCode = 404 - res.end('Not found') - } - - validateClient (params) { - return params.get('client_id') === this.clientId - && params.get('client_secret') === this.clientSecret - } - - async handleTokenRequest (req, res) { - const body = await this.readBody(req) - const params = new URLSearchParams(body) - - if (!this.validateClient(params)) { - return this.sendOAuthError(res, 401, 'invalid_client', 'Invalid client credentials') - } - - const grantType = params.get('grant_type') - - if (grantType === 'password') { - const username = params.get('username') - const password = params.get('password') - if (username !== this.username || password !== this.password) { - return this.sendOAuthError(res, 401, 'invalid_grant', 'Invalid user credentials') - } - - return this.sendTokenResponse(res, await this.buildUserClaims(username)) - } - - if (grantType === 'refresh_token') { - const refreshToken = params.get('refresh_token') - const claims = this.refreshTokens.get(refreshToken) - if (!claims) { - return this.sendOAuthError(res, 401, 'invalid_grant', 'Invalid refresh token') - } - - return this.sendTokenResponse(res, claims) - } - - return this.sendOAuthError(res, 400, 'unsupported_grant_type', 'Unsupported grant type') - } - - async handleUserInfoRequest (req, res) { - const authHeader = req.headers.authorization || '' - if (!authHeader.startsWith('Bearer ')) { - return this.sendOAuthError(res, 401, 'invalid_token', 'Missing bearer token') - } - - try { - const accessToken = authHeader.slice('Bearer '.length).trim() - const claims = decodeJwt(accessToken) - return this.sendJson(res, { - sub: claims.sub, - preferred_username: claims.preferred_username, - email: claims.email, - roles: claims.roles - }) - } catch (error) { - return this.sendOAuthError(res, 401, 'invalid_token', 'Invalid bearer token') - } - } - - async buildUserClaims (username) { - return { - sub: 'test-user-id', - preferred_username: username, - email: `${username}@example.com`, - roles: ['SRE', 'Viewer'] - } - } - - async sendTokenResponse (res, claims) { - const accessToken = await this.issueAccessToken(claims) - const refreshToken = `mock-refresh-${claims.sub}-${Date.now()}` - this.refreshTokens.set(refreshToken, claims) - - return this.sendJson(res, { - access_token: accessToken, - refresh_token: refreshToken, - token_type: 'Bearer', - expires_in: 3600 - }) - } - - sendOAuthError (res, statusCode, error, errorDescription) { - res.statusCode = statusCode - return this.sendJson(res, { - error, - error_description: errorDescription - }) - } - - sendJson (res, body) { - res.setHeader('Content-Type', 'application/json') - res.end(JSON.stringify(body)) - } - - async issueAccessToken (claims = {}) { - const now = Math.floor(Date.now() / 1000) - const payload = { - sub: 'test-user-id', - preferred_username: 'test-user', - iss: this.issuer, - aud: this.clientId, - exp: now + 3600, - iat: now, - ...claims - } - - return new SignJWT(payload) - .setProtectedHeader({ alg: 'RS256', kid: this.kid }) - .sign(this.privateKey) - } - - getEnv () { - return { - OIDC_ISSUER_URL: this.issuer, - OIDC_CLIENT_ID: this.clientId, - OIDC_CLIENT_SECRET: this.clientSecret - } - } - - async stop () { - if (!this.server) { - return - } - - if (typeof this.server.closeAllConnections === 'function') { - this.server.closeAllConnections() - } - - await new Promise((resolve, reject) => { - this.server.close((error) => (error ? reject(error) : resolve())) - }) - this.server = null - this.refreshTokens.clear() - } -} - -module.exports = { - MockOidcProvider -} diff --git a/test/support/oidc-smoke.js b/test/support/oidc-smoke.js deleted file mode 100644 index 157ed3db..00000000 --- a/test/support/oidc-smoke.js +++ /dev/null @@ -1,40 +0,0 @@ -const { createRemoteJWKSet, jwtVerify } = require('jose') -const oidcClient = require('openid-client') - -/** - * Generic OIDC smoke helper: discovery + JWKS fetch + JWT verify against MockOidcProvider. - */ -async function verifyMockAccessToken (provider, claims = {}) { - const issuer = new URL(provider.issuer) - const configuration = await oidcClient.discovery( - issuer, - provider.clientId, - provider.clientSecret - ) - const metadata = configuration.serverMetadata() - const jwks = createRemoteJWKSet(new URL(metadata.jwks_uri)) - const token = await provider.issueAccessToken(claims) - const verifyOptions = { issuer: metadata.issuer } - if (provider.clientId) { - verifyOptions.audience = provider.clientId - } - - const { payload } = await jwtVerify(token, jwks, verifyOptions) - return { token, payload } -} - -function buildKauthGrant (payload, token) { - return { - grant: { - access_token: { - token, - content: payload - } - } - } -} - -module.exports = { - verifyMockAccessToken, - buildKauthGrant -} diff --git a/test/support/oidc-test-helpers.js b/test/support/oidc-test-helpers.js index 755e8d82..47cc3972 100644 --- a/test/support/oidc-test-helpers.js +++ b/test/support/oidc-test-helpers.js @@ -1,12 +1,13 @@ const OIDC_ENV_KEYS = [ + 'AUTH_MODE', + 'CONTROLLER_PUBLIC_URL', 'OIDC_ISSUER_URL', 'OIDC_CLIENT_ID', 'OIDC_CLIENT_SECRET', - 'OIDC_VIEWER_CLIENT_ID' + 'OIDC_VIEWER_CLIENT_ID', + 'AUTH_VIEWER_CLIENT_ENABLED' ] -let savedTlsRejectUnauthorized - function snapshotOidcEnv () { return OIDC_ENV_KEYS.reduce((env, key) => { env[key] = process.env[key] @@ -34,20 +35,6 @@ function applyOidcEnv (env = {}) { } } -function enableMockOidcTls () { - savedTlsRejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' -} - -function restoreMockOidcTls () { - if (savedTlsRejectUnauthorized === undefined) { - delete process.env.NODE_TLS_REJECT_UNAUTHORIZED - } else { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = savedTlsRejectUnauthorized - } - savedTlsRejectUnauthorized = undefined -} - function reloadOidcModule () { const oidcPath = require.resolve('../../src/config/oidc') delete require.cache[oidcPath] @@ -85,8 +72,6 @@ module.exports = { snapshotOidcEnv, restoreOidcEnv, applyOidcEnv, - enableMockOidcTls, - restoreMockOidcTls, reloadOidcModule, runMiddleware } From 7e40f331f25426a145877d8f7a2dd660bd630d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 17:04:10 +0300 Subject: [PATCH 46/75] Add auth BFF session, interaction state, and bootstrap secret schema. Introduce AuthBffSessions and AuthInteractionStates tables, persist session secret reference on AuthBootstrapMeta, and set default refresh token TTL to one hour in greenfield seeders and migrations. --- .../mysql/db_migration_mysql_v3.8.0.sql | 23 +++++- .../postgres/db_migration_pg_v3.8.0.sql | 23 +++++- .../sqlite/db_migration_sqlite_v3.8.0.sql | 23 +++++- src/data/models/authBffSession.js | 31 ++++++++ src/data/models/authBootstrapMeta.js | 5 ++ src/data/models/authInteractionState.js | 31 ++++++++ .../seeders/mysql/db_seeder_mysql_v3.8.0.sql | 2 +- .../seeders/postgres/db_seeder_pg_v3.8.0.sql | 2 +- .../sqlite/db_seeder_sqlite_v3.8.0.sql | 2 +- src/data/stores/sequelize-session-store.js | 73 +++++++++++++++++++ 10 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 src/data/models/authBffSession.js create mode 100644 src/data/models/authInteractionState.js create mode 100644 src/data/stores/sequelize-session-store.js diff --git a/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql b/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql index 9ce38476..11e91784 100644 --- a/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql +++ b/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql @@ -1091,10 +1091,31 @@ CREATE INDEX idx_auth_oidc_provider_states_uid ON AuthOidcProviderStates (uid); CREATE INDEX idx_auth_oidc_provider_states_user_code ON AuthOidcProviderStates (user_code); CREATE INDEX idx_auth_oidc_provider_states_expires_at ON AuthOidcProviderStates (expires_at); +CREATE TABLE IF NOT EXISTS AuthBffSessions ( + sid VARCHAR(255) PRIMARY KEY NOT NULL, + data TEXT NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_bff_sessions_expires_at ON AuthBffSessions (expires_at); + +CREATE TABLE IF NOT EXISTS AuthInteractionStates ( + uid VARCHAR(255) PRIMARY KEY NOT NULL, + payload TEXT NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_interaction_states_expires_at ON AuthInteractionStates (expires_at); + CREATE TABLE IF NOT EXISTS AuthBootstrapMeta ( id INT AUTO_INCREMENT PRIMARY KEY, completed_at DATETIME, bootstrap_admin_user_id VARCHAR(36), + session_secret_ref TEXT, created_at DATETIME, updated_at DATETIME, FOREIGN KEY (bootstrap_admin_user_id) REFERENCES AuthUsers (id) ON DELETE SET NULL @@ -1111,7 +1132,7 @@ CREATE TABLE IF NOT EXISTS AuthPolicy ( max_failed_attempts INT DEFAULT 5, lockout_duration_minutes INT DEFAULT 15, access_token_ttl_seconds INT DEFAULT 900, - refresh_token_ttl_seconds INT DEFAULT 604800, + refresh_token_ttl_seconds INT DEFAULT 3600, refresh_rotation BOOLEAN DEFAULT true, max_concurrent_sessions INT, created_at DATETIME, diff --git a/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql b/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql index 4ae51e65..1114bc14 100644 --- a/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql +++ b/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql @@ -1083,10 +1083,31 @@ CREATE INDEX idx_auth_oidc_provider_states_uid ON "AuthOidcProviderStates" (uid) CREATE INDEX idx_auth_oidc_provider_states_user_code ON "AuthOidcProviderStates" (user_code); CREATE INDEX idx_auth_oidc_provider_states_expires_at ON "AuthOidcProviderStates" (expires_at); +CREATE TABLE IF NOT EXISTS "AuthBffSessions" ( + sid VARCHAR(255) PRIMARY KEY NOT NULL, + data TEXT NOT NULL, + expires_at TIMESTAMP NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); + +CREATE INDEX idx_auth_bff_sessions_expires_at ON "AuthBffSessions" (expires_at); + +CREATE TABLE IF NOT EXISTS "AuthInteractionStates" ( + uid VARCHAR(255) PRIMARY KEY NOT NULL, + payload TEXT NOT NULL, + expires_at TIMESTAMP NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); + +CREATE INDEX idx_auth_interaction_states_expires_at ON "AuthInteractionStates" (expires_at); + CREATE TABLE IF NOT EXISTS "AuthBootstrapMeta" ( id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, completed_at TIMESTAMP(0), bootstrap_admin_user_id VARCHAR(36), + session_secret_ref TEXT, created_at TIMESTAMP(0), updated_at TIMESTAMP(0), FOREIGN KEY (bootstrap_admin_user_id) REFERENCES "AuthUsers" (id) ON DELETE SET NULL @@ -1103,7 +1124,7 @@ CREATE TABLE IF NOT EXISTS "AuthPolicy" ( max_failed_attempts INT DEFAULT 5, lockout_duration_minutes INT DEFAULT 15, access_token_ttl_seconds INT DEFAULT 900, - refresh_token_ttl_seconds INT DEFAULT 604800, + refresh_token_ttl_seconds INT DEFAULT 3600, refresh_rotation BOOLEAN DEFAULT true, max_concurrent_sessions INT, created_at TIMESTAMP(0), diff --git a/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql b/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql index 0372ace7..690a813e 100644 --- a/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql +++ b/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql @@ -1082,10 +1082,31 @@ CREATE INDEX idx_auth_oidc_provider_states_uid ON AuthOidcProviderStates (uid); CREATE INDEX idx_auth_oidc_provider_states_user_code ON AuthOidcProviderStates (user_code); CREATE INDEX idx_auth_oidc_provider_states_expires_at ON AuthOidcProviderStates (expires_at); +CREATE TABLE IF NOT EXISTS AuthBffSessions ( + sid VARCHAR(255) PRIMARY KEY NOT NULL, + data TEXT NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_bff_sessions_expires_at ON AuthBffSessions (expires_at); + +CREATE TABLE IF NOT EXISTS AuthInteractionStates ( + uid VARCHAR(255) PRIMARY KEY NOT NULL, + payload TEXT NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_interaction_states_expires_at ON AuthInteractionStates (expires_at); + CREATE TABLE IF NOT EXISTS AuthBootstrapMeta ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, completed_at DATETIME, bootstrap_admin_user_id VARCHAR(36), + session_secret_ref TEXT, created_at DATETIME, updated_at DATETIME, FOREIGN KEY (bootstrap_admin_user_id) REFERENCES AuthUsers (id) ON DELETE SET NULL @@ -1102,7 +1123,7 @@ CREATE TABLE IF NOT EXISTS AuthPolicy ( max_failed_attempts INT DEFAULT 5, lockout_duration_minutes INT DEFAULT 15, access_token_ttl_seconds INT DEFAULT 900, - refresh_token_ttl_seconds INT DEFAULT 604800, + refresh_token_ttl_seconds INT DEFAULT 3600, refresh_rotation BOOLEAN DEFAULT true, max_concurrent_sessions INT, created_at DATETIME, diff --git a/src/data/models/authBffSession.js b/src/data/models/authBffSession.js new file mode 100644 index 00000000..a8476b39 --- /dev/null +++ b/src/data/models/authBffSession.js @@ -0,0 +1,31 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthBffSession = sequelize.define('AuthBffSession', { + sid: { + type: DataTypes.STRING(255), + primaryKey: true, + allowNull: false, + field: 'sid' + }, + data: { + type: DataTypes.TEXT, + allowNull: false, + field: 'data' + }, + expiresAt: { + type: DataTypes.DATE, + allowNull: false, + field: 'expires_at' + } + }, { + tableName: 'AuthBffSessions', + timestamps: true, + underscored: true, + indexes: [ + { fields: ['expires_at'] } + ] + }) + + return AuthBffSession +} diff --git a/src/data/models/authBootstrapMeta.js b/src/data/models/authBootstrapMeta.js index 06041b42..5e73ab46 100644 --- a/src/data/models/authBootstrapMeta.js +++ b/src/data/models/authBootstrapMeta.js @@ -18,6 +18,11 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.STRING(36), allowNull: true, field: 'bootstrap_admin_user_id' + }, + sessionSecretRef: { + type: DataTypes.TEXT, + allowNull: true, + field: 'session_secret_ref' } }, { tableName: 'AuthBootstrapMeta', diff --git a/src/data/models/authInteractionState.js b/src/data/models/authInteractionState.js new file mode 100644 index 00000000..8a9cfb83 --- /dev/null +++ b/src/data/models/authInteractionState.js @@ -0,0 +1,31 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthInteractionState = sequelize.define('AuthInteractionState', { + uid: { + type: DataTypes.STRING(255), + primaryKey: true, + allowNull: false, + field: 'uid' + }, + payload: { + type: DataTypes.TEXT, + allowNull: false, + field: 'payload' + }, + expiresAt: { + type: DataTypes.DATE, + allowNull: false, + field: 'expires_at' + } + }, { + tableName: 'AuthInteractionStates', + timestamps: true, + underscored: true, + indexes: [ + { fields: ['expires_at'] } + ] + }) + + return AuthInteractionState +} diff --git a/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql b/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql index 5a6e9fcc..71ab9656 100644 --- a/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql +++ b/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql @@ -63,7 +63,7 @@ INSERT IGNORE INTO AuthPolicy ( refresh_rotation, max_concurrent_sessions ) -VALUES (1, 12, true, true, true, 0, 5, 5, 15, 900, 604800, true, NULL); +VALUES (1, 12, true, true, true, 0, 5, 5, 15, 900, 3600, true, NULL); INSERT IGNORE INTO AuthGroups (name, is_system) VALUES diff --git a/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql b/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql index 976f4647..0dfb66fe 100644 --- a/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql +++ b/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql @@ -63,7 +63,7 @@ INSERT INTO "AuthPolicy" ( refresh_rotation, max_concurrent_sessions ) -VALUES (1, 12, true, true, true, 0, 5, 5, 15, 900, 604800, true, NULL) +VALUES (1, 12, true, true, true, 0, 5, 5, 15, 900, 3600, true, NULL) ON CONFLICT (id) DO NOTHING; INSERT INTO "AuthGroups" (name, is_system) diff --git a/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql b/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql index 5647391d..8cf2c81c 100644 --- a/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql +++ b/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql @@ -61,7 +61,7 @@ INSERT OR IGNORE INTO AuthPolicy ( refresh_rotation, max_concurrent_sessions ) -VALUES (1, 12, true, true, true, 0, 5, 5, 15, 900, 604800, true, NULL); +VALUES (1, 12, true, true, true, 0, 5, 5, 15, 900, 3600, true, NULL); INSERT OR IGNORE INTO AuthGroups (name, is_system) VALUES diff --git a/src/data/stores/sequelize-session-store.js b/src/data/stores/sequelize-session-store.js new file mode 100644 index 00000000..d54359ee --- /dev/null +++ b/src/data/stores/sequelize-session-store.js @@ -0,0 +1,73 @@ +'use strict' + +const { Store } = require('express-session') +const { Op } = require('sequelize') + +class SequelizeSessionStore extends Store { + constructor ({ model, ttlMs }) { + super() + this.model = model + this.ttlMs = ttlMs + } + + _expiresAt () { + return new Date(Date.now() + this.ttlMs) + } + + get (sid, callback) { + this.model.findByPk(sid) + .then((row) => { + if (!row || (row.expiresAt && row.expiresAt <= new Date())) { + return callback(null, null) + } + try { + return callback(null, JSON.parse(row.data)) + } catch (error) { + return callback(error) + } + }) + .catch((error) => callback(error)) + } + + set (sid, session, callback) { + const expiresAt = this._expiresAt() + const data = JSON.stringify(session) + + this.model.upsert({ + sid, + data, + expiresAt + }) + .then(() => callback(null)) + .catch((error) => callback(error)) + } + + destroy (sid, callback) { + this.model.destroy({ where: { sid } }) + .then(() => callback(null)) + .catch((error) => callback(error)) + } + + touch (sid, session, callback) { + this.model.update({ + data: JSON.stringify(session), + expiresAt: this._expiresAt() + }, { + where: { sid } + }) + .then(() => callback(null)) + .catch((error) => callback(error)) + } + + async purgeExpired () { + await this.model.destroy({ + where: { + expiresAt: { + [Op.lte]: new Date() + } + } + }) + } +} + +module.exports = SequelizeSessionStore From 27743b4a3297557b19fdec0053cfd7198fbdbf77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 17:04:14 +0300 Subject: [PATCH 47/75] Add embedded OAuth interaction APIs for browser login. Expose interaction login, MFA, enroll, change-password, and complete routes wired to oidc-provider redirects and shared interaction state storage. --- src/config/embedded-oidc.js | 147 ++++----- src/controllers/user-controller.js | 48 ++- src/helpers/constants.js | 1 + src/helpers/errors.js | 11 +- src/routes/user.js | 231 ++++++++++++- src/schemas/user.js | 29 +- src/services/auth-interaction-service.js | 310 ++++++++++++++++++ src/services/auth-interaction-state-store.js | 105 ++++++ .../services/auth-interaction-service.test.js | 247 ++++++++++++++ 9 files changed, 1031 insertions(+), 98 deletions(-) create mode 100644 src/services/auth-interaction-service.js create mode 100644 src/services/auth-interaction-state-store.js create mode 100644 test/src/services/auth-interaction-service.test.js diff --git a/src/config/embedded-oidc.js b/src/config/embedded-oidc.js index 425dc80c..d20ecdbc 100644 --- a/src/config/embedded-oidc.js +++ b/src/config/embedded-oidc.js @@ -1,21 +1,42 @@ 'use strict' const crypto = require('crypto') -const { Provider } = require('oidc-provider') +const { Provider, interactionPolicy } = require('oidc-provider') const { generateKeyPair, exportJWK } = require('jose') const config = require('./index') const logger = require('../logger') const secretHelper = require('../helpers/secret-helper') +const { + resolveConfidentialClientSecret +} = require('./embedded-oidc-client-secret') const { getOidcSettings } = require('./oidc') const { createOidcProviderAdapterFactory } = require('../data/adapters/oidc-provider-adapter') +const { buildUserAccessClaims } = require('../services/auth-token-service') +const { loadOidcProviderTtls } = require('./auth-oidc-ttl') +const { getPublicUrl, getViewerUrl } = require('./auth-urls') const DEFAULT_VIEWER_CLIENT_ID = 'ecn-viewer' -const CONTROLLER_CLIENT_ID = 'controller' let providerInstance = null -function getPublicUrl () { - return (process.env.CONTROLLER_PUBLIC_URL || config.get('server.publicUrl') || '').replace(/\/$/, '') +function getOauthInteractionPath () { + return config.get('auth.oauthInteractionUrl') || '/login/oauth' +} + +function buildInteractionRedirectUrl (interactionUid) { + const viewerUrl = getViewerUrl() + if (!viewerUrl) { + throw new Error('CONTROLLER_PUBLIC_URL or VIEWER_URL is required for embedded OAuth BFF interactions') + } + const interactionPath = getOauthInteractionPath() + const normalizedPath = interactionPath.startsWith('/') ? interactionPath : `/${interactionPath}` + return `${viewerUrl}${normalizedPath}?interaction=${encodeURIComponent(interactionUid)}` +} + +function buildInteractionPolicy () { + const policy = interactionPolicy.base() + policy.remove('consent') + return policy } function isViewerClientEnabled () { @@ -33,10 +54,6 @@ function getViewerClientId () { DEFAULT_VIEWER_CLIENT_ID } -function generateClientSecret () { - return crypto.randomBytes(32).toString('base64url') -} - function getCookieKeys () { const configured = process.env.OIDC_COOKIE_KEYS || config.get('auth.cookieKeys') if (Array.isArray(configured)) { @@ -48,84 +65,28 @@ function getCookieKeys () { return ['controller-embedded-oidc-cookie-key'] } -function getTrustProxySetting () { - const trustProxy = process.env.TRUST_PROXY || config.get('server.trustProxy', false) - if (trustProxy === true || trustProxy === 'true' || trustProxy === 1 || trustProxy === '1') { - return true - } - return trustProxy || false -} - -async function resolveStoredSecret (secretRef, secretName, secretType) { - if (!secretRef) { - return null - } - - if (secretHelper.isVaultReference(secretRef)) { - const data = await secretHelper.decryptSecret(secretRef, secretName, secretType) - return data.secret || data.client_secret || data.value || null - } - - try { - const data = await secretHelper.decryptSecret(secretRef, secretName, secretType) - return data.secret || data.client_secret || data.value || null - } catch (error) { - return secretRef - } -} - -async function persistClientSecret (db, clientId, secret) { - const secretRef = await secretHelper.encryptSecret({ secret }, `oidc-client-${clientId}`, 'oidc-client') - const existing = await db.AuthOidcClient.findOne({ where: { clientId } }) - if (existing) { - await existing.update({ secretRef }) - return existing - } - - return db.AuthOidcClient.create({ - clientId, - secretRef, - clientType: 'confidential' - }) -} - async function ensureConfidentialClientMetadata (db) { - const { clientId, clientSecret: envSecret } = getOidcSettings() - const resolvedClientId = clientId || CONTROLLER_CLIENT_ID const publicUrl = getPublicUrl() - let clientRow = await db.AuthOidcClient.findOne({ where: { clientId: resolvedClientId } }) - - let secret = envSecret || null - if (!secret && clientRow && clientRow.secretRef) { - secret = await resolveStoredSecret(clientRow.secretRef, `oidc-client-${resolvedClientId}`, 'oidc-client') - } - - if (!secret) { - secret = generateClientSecret() - clientRow = await persistClientSecret(db, resolvedClientId, secret) - logger.info(`Generated embedded OIDC client secret for "${resolvedClientId}"`) - } else if (!clientRow) { - let secretRef = envSecret - if (!secretRef) { - secretRef = await secretHelper.encryptSecret({ secret }, `oidc-client-${resolvedClientId}`, 'oidc-client') - } - clientRow = await db.AuthOidcClient.create({ - clientId: resolvedClientId, - secretRef, - clientType: 'confidential' - }) - } + const { clientId, clientSecret } = await resolveConfidentialClientSecret(db, { createIfMissing: true }) return { - client_id: resolvedClientId, - client_secret: secret, - grant_types: ['authorization_code', 'refresh_token', 'client_credentials'], + client_id: clientId, + client_secret: clientSecret, + grant_types: ['authorization_code'], response_types: ['code'], token_endpoint_auth_method: 'client_secret_basic', redirect_uris: [`${publicUrl}/api/v3/user/oauth/callback`] } } +function getTrustProxySetting () { + const trustProxy = process.env.TRUST_PROXY || config.get('server.trustProxy', false) + if (trustProxy === true || trustProxy === 'true' || trustProxy === 1 || trustProxy === '1') { + return true + } + return trustProxy || false +} + async function ensureViewerClientMetadata (db) { if (!isViewerClientEnabled()) { return null @@ -144,7 +105,7 @@ async function ensureViewerClientMetadata (db) { return { client_id: clientId, client_secret: undefined, - grant_types: ['authorization_code', 'refresh_token'], + grant_types: ['authorization_code'], response_types: ['code'], token_endpoint_auth_method: 'none', redirect_uris: [`${publicUrl}/`] @@ -207,14 +168,6 @@ async function ensureSigningJwks (db) { return { keys: [privateJwk] } } -async function loadTokenPolicy (db) { - const policy = await db.AuthPolicy.findByPk(1) - return { - accessTokenTtlSeconds: (policy && policy.accessTokenTtlSeconds) || 900, - refreshTokenTtlSeconds: (policy && policy.refreshTokenTtlSeconds) || 604800 - } -} - async function buildProviderConfiguration (db) { const clients = [await ensureConfidentialClientMetadata(db)] const viewerClient = await ensureViewerClientMetadata(db) @@ -222,7 +175,7 @@ async function buildProviderConfiguration (db) { clients.push(viewerClient) } - const tokenPolicy = await loadTokenPolicy(db) + const ttlPolicy = await loadOidcProviderTtls(db) const trustProxy = getTrustProxySetting() return { @@ -248,9 +201,7 @@ async function buildProviderConfiguration (db) { async claims () { return { sub: id, - email: user.email, - preferred_username: user.email, - groups: groupNames + ...buildUserAccessClaims(user, groupNames) } } } @@ -258,6 +209,12 @@ async function buildProviderConfiguration (db) { cookies: { keys: getCookieKeys() }, + interactions: { + policy: buildInteractionPolicy(), + url (ctx, interaction) { + return buildInteractionRedirectUrl(interaction.uid) + } + }, features: { devInteractions: { enabled: false }, revocation: { enabled: true }, @@ -280,8 +237,12 @@ async function buildProviderConfiguration (db) { }, proxy: trustProxy, ttl: { - AccessToken: tokenPolicy.accessTokenTtlSeconds, - RefreshToken: tokenPolicy.refreshTokenTtlSeconds + AccessToken: ttlPolicy.accessTokenTtlSeconds, + RefreshToken: ttlPolicy.refreshTokenTtlSeconds, + IdToken: ttlPolicy.idTokenTtlSeconds, + Interaction: ttlPolicy.interactionTtlSeconds, + Grant: ttlPolicy.grantTtlSeconds, + Session: ttlPolicy.sessionTtlSeconds } } } @@ -318,5 +279,7 @@ function resetEmbeddedIssuerForTests () { module.exports = { initEmbeddedIssuer, getEmbeddedProvider, - resetEmbeddedIssuerForTests + resetEmbeddedIssuerForTests, + getOauthInteractionPath, + buildInteractionRedirectUrl } diff --git a/src/controllers/user-controller.js b/src/controllers/user-controller.js index 3d876be9..936964ed 100644 --- a/src/controllers/user-controller.js +++ b/src/controllers/user-controller.js @@ -78,6 +78,45 @@ const oauthCallbackEndPoint = async function (req) { return UserService.oauthCallback(req, false) } +const interactionStatusEndPoint = async function (req) { + return UserService.interactionStatus(req.params.uid, false) +} + +const interactionLoginEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.interactionLogin) + return UserService.interactionLogin(req.params.uid, { + email: payload.email, + password: payload.password + }, false) +} + +const interactionMfaEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.interactionMfa) + return UserService.interactionMfa(req.params.uid, payload.code, false) +} + +const interactionEnrollEndPoint = async function (req) { + return UserService.interactionEnroll(req.params.uid, false) +} + +const interactionConfirmEnrollEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.interactionMfa) + return UserService.interactionConfirmEnroll(req.params.uid, payload.code, false) +} + +const interactionChangePasswordEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.changePassword) + return UserService.interactionChangePassword(req.params.uid, payload, false) +} + +const interactionCompleteEndPoint = async function (req, res) { + return UserService.interactionComplete(req.params.uid, req, res, false) +} + module.exports = { userLoginEndPoint: userLoginEndPoint, refreshTokenEndPoint: refreshTokenEndPoint, @@ -88,5 +127,12 @@ module.exports = { disableMfaEndPoint: disableMfaEndPoint, changePasswordEndPoint: changePasswordEndPoint, oauthAuthorizeEndPoint: oauthAuthorizeEndPoint, - oauthCallbackEndPoint: oauthCallbackEndPoint + oauthCallbackEndPoint: oauthCallbackEndPoint, + interactionStatusEndPoint: interactionStatusEndPoint, + interactionLoginEndPoint: interactionLoginEndPoint, + interactionMfaEndPoint: interactionMfaEndPoint, + interactionEnrollEndPoint: interactionEnrollEndPoint, + interactionConfirmEnrollEndPoint: interactionConfirmEnrollEndPoint, + interactionChangePasswordEndPoint: interactionChangePasswordEndPoint, + interactionCompleteEndPoint: interactionCompleteEndPoint } diff --git a/src/helpers/constants.js b/src/helpers/constants.js index 0725a040..9544d78a 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -61,6 +61,7 @@ module.exports = { HTTP_CODE_UNAUTHORIZED: 401, HTTP_CODE_FORBIDDEN: 403, HTTP_CODE_NOT_FOUND: 404, + HTTP_CODE_TOO_MANY_REQUESTS: 429, HTTP_CODE_NOT_IMPLEMENTED: 501, HTTP_CODE_DUPLICATE_PROPERTY: 409, HTTP_CODE_INTERNAL_ERROR: 500, diff --git a/src/helpers/errors.js b/src/helpers/errors.js index aef56300..7efe4d9e 100644 --- a/src/helpers/errors.js +++ b/src/helpers/errors.js @@ -126,6 +126,14 @@ class NotImplementedError extends Error { } } +class RateLimitExceededError extends Error { + constructor (message = 'Too many authentication requests from this IP address') { + super(message) + this.message = message + this.name = 'RateLimitExceededError' + } +} + module.exports = { AuthenticationError: AuthenticationError, TransactionError: TransactionError, @@ -140,5 +148,6 @@ module.exports = { CLIArgsNotProvidedError: CLIArgsNotProvidedError, ConflictError: ConflictError, ForbiddenError: ForbiddenError, - NotImplementedError: NotImplementedError + NotImplementedError: NotImplementedError, + RateLimitExceededError: RateLimitExceededError } diff --git a/src/routes/user.js b/src/routes/user.js index c6732ac4..5ad4cd09 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -223,11 +223,13 @@ module.exports = [ errorCodes ) const responseObject = await getUserProfileEndPoint(req) - const user = req.kauth.grant.access_token.content.preferred_username res .status(responseObject.code) .send(responseObject.body) + const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token + ? req.kauth.grant.access_token.content.preferred_username + : undefined logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) } }, @@ -338,5 +340,232 @@ module.exports = [ logger.apiRes('GET /api/v3/user/oauth/callback', { args: { statusCode: responseObject.code } }) } + }, + { + method: 'get', + path: '/api/v3/user/interaction/:uid', + middleware: async (req, res) => { + logger.apiReq('GET /api/v3/user/interaction/:uid') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionStatusEndPoint = ResponseDecorator.handleErrors( + UserController.interactionStatusEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionStatusEndPoint(req) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('GET /api/v3/user/interaction/:uid', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/interaction/:uid/login', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/interaction/:uid/login') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionLoginEndPoint = ResponseDecorator.handleErrors( + UserController.interactionLoginEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionLoginEndPoint(req) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('POST /api/v3/user/interaction/:uid/login', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/interaction/:uid/mfa', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/interaction/:uid/mfa') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionMfaEndPoint = ResponseDecorator.handleErrors( + UserController.interactionMfaEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionMfaEndPoint(req) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('POST /api/v3/user/interaction/:uid/mfa', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/interaction/:uid/enroll', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/interaction/:uid/enroll') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionEnrollEndPoint = ResponseDecorator.handleErrors( + UserController.interactionEnrollEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionEnrollEndPoint(req) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('POST /api/v3/user/interaction/:uid/enroll', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/interaction/:uid/confirm-enroll', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/interaction/:uid/confirm-enroll') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionConfirmEnrollEndPoint = ResponseDecorator.handleErrors( + UserController.interactionConfirmEnrollEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionConfirmEnrollEndPoint(req) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('POST /api/v3/user/interaction/:uid/confirm-enroll', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/interaction/:uid/change-password', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/interaction/:uid/change-password') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError, Errors.InvalidArgumentError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionChangePasswordEndPoint = ResponseDecorator.handleErrors( + UserController.interactionChangePasswordEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionChangePasswordEndPoint(req) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('POST /api/v3/user/interaction/:uid/change-password', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/interaction/:uid/complete', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/interaction/:uid/complete') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionCompleteEndPoint = ResponseDecorator.handleErrors( + UserController.interactionCompleteEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionCompleteEndPoint(req, res) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('POST /api/v3/user/interaction/:uid/complete', { args: { statusCode: responseObject.code } }) + } } ] diff --git a/src/schemas/user.js b/src/schemas/user.js index c2f1ecb7..638491da 100644 --- a/src/schemas/user.js +++ b/src/schemas/user.js @@ -17,8 +17,7 @@ const login = { properties: { email: { type: 'string', - pattern: '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + - '\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$' + minLength: 1 }, password: { type: 'string' }, totp: { type: 'string' } @@ -70,6 +69,30 @@ const changePassword = { additionalProperties: true } +const interactionLogin = { + id: '/interactionLogin', + type: 'object', + properties: { + email: { + type: 'string', + minLength: 1 + }, + password: { type: 'string' } + }, + required: ['email', 'password'], + additionalProperties: true +} + +const interactionMfa = { + id: '/interactionMfa', + type: 'object', + properties: { + code: { type: 'string' } + }, + required: ['code'], + additionalProperties: true +} + const createAuthUser = { id: '/createAuthUser', type: 'object', @@ -127,6 +150,6 @@ const updateAuthGroup = { } module.exports = { - mainSchemas: [login, refresh, mfaConfirm, mfaDisable, changePassword, createAuthUser, updateAuthUser, createAuthGroup, updateAuthGroup], + mainSchemas: [login, refresh, mfaConfirm, mfaDisable, changePassword, interactionLogin, interactionMfa, createAuthUser, updateAuthUser, createAuthGroup, updateAuthGroup], innerSchemas: [] } diff --git a/src/services/auth-interaction-service.js b/src/services/auth-interaction-service.js new file mode 100644 index 00000000..82ff8263 --- /dev/null +++ b/src/services/auth-interaction-service.js @@ -0,0 +1,310 @@ +'use strict' + +const db = require('../data/models') +const Errors = require('../helpers/errors') +const { withTransaction } = require('../helpers/app-helper') +const { getAuthMode } = require('../config/oidc') +const embeddedOidc = require('../config/embedded-oidc') +const AuthPolicyService = require('./auth-policy-service') +const AuthPasswordService = require('./auth-password-service') +const AuthMfaService = require('./auth-mfa-service') +const AuthUserService = require('./auth-user-service') +const InteractionStateStore = require('./auth-interaction-state-store') + +function ensureEmbeddedMode () { + if (getAuthMode() !== 'embedded') { + throw new Errors.NotImplementedError('OAuth interactions are only available in embedded auth mode') + } +} + +function getProvider () { + const provider = embeddedOidc.getEmbeddedProvider() + if (!provider) { + throw new Error('Embedded OIDC provider is not initialized') + } + return provider +} + +const { + getInteractionState, + setInteractionState, + clearInteractionState, + resetInteractionStateForTests +} = InteractionStateStore + +async function findInteraction (uid) { + const provider = getProvider() + const interaction = await provider.Interaction.find(uid) + if (!interaction) { + throw new Errors.AuthenticationError('Interaction session not found or expired') + } + return interaction +} + +async function finishInteraction (interaction, result) { + interaction.result = result + if (typeof interaction.exp !== 'number') { + throw new Errors.AuthenticationError('Interaction session not found or expired') + } + const ttlSeconds = interaction.exp - Math.floor(Date.now() / 1000) + await interaction.save(ttlSeconds) + return interaction.returnTo +} + +async function loadAuthContextByUserId (userId, transaction) { + const user = await db.AuthUser.findOne(withTransaction(transaction, { + where: { + id: userId, + deletedAt: null + }, + include: [ + { + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }, + { + model: db.AuthMfa, + as: 'mfa' + } + ] + })) + + if (!user) { + return null + } + + return { + user, + groups: user.groups || [], + mfa: user.mfa || null, + groupNames: (user.groups || []).map((group) => String(group.name).toLowerCase()) + } +} + +function resolveNextStep (authContext, state) { + if (!authContext || !state || !state.userId) { + return 'login' + } + + const { user, groups, mfa } = authContext + + if (AuthMfaService.userMustEnrollMfa(user, groups, mfa)) { + if (!state.enrollmentConfirmed) { + return state.enrollmentStarted ? 'confirm-enroll' : 'enroll' + } + } + + if (AuthMfaService.userRequiresMfaChallenge(user, groups, mfa)) { + if (!state.mfaVerified) { + return 'mfa' + } + } + + if (user.mustChangePassword && !state.passwordChanged) { + return 'change-password' + } + + return 'complete' +} + +async function verifyLoginCredentials (credentials, transaction) { + const authContext = await AuthMfaService.loadUserAuthContext(credentials.email, transaction) + if (!authContext) { + throw new Errors.InvalidCredentialsError() + } + + const { user } = authContext + const policy = await AuthPolicyService.getPolicy(transaction) + + if (AuthPolicyService.isAccountLocked(user, policy)) { + throw new Errors.InvalidCredentialsError() + } + + if (!await AuthPasswordService.verifyPassword(credentials.password, user.passwordHash)) { + await AuthPolicyService.recordFailedLogin(user, policy, transaction) + throw new Errors.InvalidCredentialsError() + } + + return authContext +} + +async function getStatus (uid, transaction) { + ensureEmbeddedMode() + await findInteraction(uid) + + const state = await getInteractionState(uid) + if (!state || !state.userId) { + return { step: 'login' } + } + + const authContext = await loadAuthContextByUserId(state.userId, transaction) + if (!authContext) { + await clearInteractionState(uid) + throw new Errors.AuthenticationError('Interaction session not found or expired') + } + + return { step: resolveNextStep(authContext, state) } +} + +async function submitLogin (uid, credentials, transaction) { + ensureEmbeddedMode() + await findInteraction(uid) + + const authContext = await verifyLoginCredentials(credentials, transaction) + const state = await setInteractionState(uid, { + userId: authContext.user.id, + mfaVerified: false, + enrollmentStarted: false, + enrollmentConfirmed: false, + passwordChanged: false + }) + + return { step: resolveNextStep(authContext, state) } +} + +async function submitMfa (uid, code, transaction) { + ensureEmbeddedMode() + await findInteraction(uid) + + const state = await getInteractionState(uid) + if (!state || !state.userId) { + throw new Errors.InvalidCredentialsError() + } + + await AuthMfaService.verifyMfaCode(state.userId, code, transaction) + const nextState = await setInteractionState(uid, { mfaVerified: true }) + const authContext = await loadAuthContextByUserId(state.userId, transaction) + + return { step: resolveNextStep(authContext, nextState) } +} + +async function submitEnroll (uid, transaction) { + ensureEmbeddedMode() + await findInteraction(uid) + + const state = await getInteractionState(uid) + if (!state || !state.userId) { + throw new Errors.InvalidCredentialsError() + } + + const enrollment = await AuthMfaService.enrollMfa(state.userId, transaction) + const nextState = await setInteractionState(uid, { enrollmentStarted: true }) + + return { + step: resolveNextStep(await loadAuthContextByUserId(state.userId, transaction), nextState), + secret: enrollment.secret, + otpauthUrl: enrollment.otpauthUrl + } +} + +async function submitConfirmEnroll (uid, code, transaction) { + ensureEmbeddedMode() + await findInteraction(uid) + + const state = await getInteractionState(uid) + if (!state || !state.userId) { + throw new Errors.InvalidCredentialsError() + } + + const result = await AuthMfaService.confirmMfa(state.userId, code, transaction) + const nextState = await setInteractionState(uid, { + enrollmentConfirmed: true, + mfaVerified: true + }) + const authContext = await loadAuthContextByUserId(state.userId, transaction) + + return { + step: resolveNextStep(authContext, nextState), + recoveryCodes: result.recoveryCodes + } +} + +async function submitChangePassword (uid, credentials, transaction) { + ensureEmbeddedMode() + await findInteraction(uid) + + const state = await getInteractionState(uid) + if (!state || !state.userId) { + throw new Errors.InvalidCredentialsError() + } + + const authContext = await loadAuthContextByUserId(state.userId, transaction) + if (!authContext) { + await clearInteractionState(uid) + throw new Errors.AuthenticationError('Interaction session not found or expired') + } + + const step = resolveNextStep(authContext, state) + if (step !== 'change-password') { + throw new Errors.ValidationError(`Interaction step "${step}" is required before password change`) + } + + await AuthUserService.changePasswordWithCurrent( + state.userId, + credentials.currentPassword, + credentials.newPassword, + transaction + ) + + const nextState = await setInteractionState(uid, { passwordChanged: true }) + const updatedContext = await loadAuthContextByUserId(state.userId, transaction) + + return { step: resolveNextStep(updatedContext, nextState) } +} + +async function buildConsentGrant (provider, interaction, accountId) { + const grant = new provider.Grant({ + accountId, + clientId: interaction.params.client_id + }) + const scope = interaction.params.scope || 'openid profile email groups' + grant.addOIDCScope(scope) + const grantId = await grant.save() + return grantId +} + +async function complete (uid, req, res, transaction) { + ensureEmbeddedMode() + + const interaction = await findInteraction(uid) + const state = await getInteractionState(uid) + if (!state || !state.userId) { + throw new Errors.InvalidCredentialsError() + } + + const authContext = await loadAuthContextByUserId(state.userId, transaction) + if (!authContext) { + await clearInteractionState(uid) + throw new Errors.AuthenticationError('Interaction session not found or expired') + } + + const step = resolveNextStep(authContext, state) + if (step !== 'complete') { + throw new Errors.ValidationError(`Interaction step "${step}" is required before completion`) + } + + const provider = getProvider() + const grantId = await buildConsentGrant(provider, interaction, state.userId) + const redirectTo = await finishInteraction(interaction, { + login: { accountId: state.userId }, + consent: { grantId } + }) + + await AuthPolicyService.resetFailedLogin(authContext.user, transaction) + await clearInteractionState(uid) + + return { redirectTo, step: 'complete' } +} + +module.exports = { + getStatus, + submitLogin, + submitMfa, + submitEnroll, + submitConfirmEnroll, + submitChangePassword, + complete, + resolveNextStep, + resetInteractionStateForTests +} diff --git a/src/services/auth-interaction-state-store.js b/src/services/auth-interaction-state-store.js new file mode 100644 index 00000000..aff76217 --- /dev/null +++ b/src/services/auth-interaction-state-store.js @@ -0,0 +1,105 @@ +'use strict' + +const { Op } = require('sequelize') +const { isSharedSessionStore, getSessionStoreTtlMs } = require('../config/auth-session-store') + +const interactionStateByUid = new Map() + +function getTtlMs () { + return getSessionStoreTtlMs() +} + +function usesDatabaseStore () { + return isSharedSessionStore() +} + +function getInteractionStateModel () { + const db = require('../data/models') + if (!db.AuthInteractionState) { + throw new Error('Database interaction state store requires AuthInteractionState model') + } + return db.AuthInteractionState +} + +async function pruneExpiredInteractionState () { + const now = Date.now() + const ttlMs = getTtlMs() + + if (usesDatabaseStore()) { + const AuthInteractionState = getInteractionStateModel() + await AuthInteractionState.destroy({ + where: { + expiresAt: { + [Op.lte]: new Date() + } + } + }) + return + } + + for (const [uid, state] of interactionStateByUid.entries()) { + if (!state.updatedAt || now - state.updatedAt > ttlMs) { + interactionStateByUid.delete(uid) + } + } +} + +async function getInteractionState (uid) { + await pruneExpiredInteractionState() + + if (usesDatabaseStore()) { + const AuthInteractionState = getInteractionStateModel() + const row = await AuthInteractionState.findByPk(uid) + if (!row || row.expiresAt <= new Date()) { + return null + } + return JSON.parse(row.payload) + } + + return interactionStateByUid.get(uid) || null +} + +async function setInteractionState (uid, patch) { + const existing = (await getInteractionState(uid)) || { updatedAt: Date.now() } + const next = { + ...existing, + ...patch, + updatedAt: Date.now() + } + + if (usesDatabaseStore()) { + const AuthInteractionState = getInteractionStateModel() + const expiresAt = new Date(Date.now() + getTtlMs()) + await AuthInteractionState.upsert({ + uid, + payload: JSON.stringify(next), + expiresAt + }) + return next + } + + interactionStateByUid.set(uid, next) + return next +} + +async function clearInteractionState (uid) { + if (usesDatabaseStore()) { + const AuthInteractionState = getInteractionStateModel() + await AuthInteractionState.destroy({ where: { uid } }) + return + } + + interactionStateByUid.delete(uid) +} + +function resetInteractionStateForTests () { + interactionStateByUid.clear() +} + +module.exports = { + getInteractionState, + setInteractionState, + clearInteractionState, + resetInteractionStateForTests, + usesDatabaseStore +} diff --git a/test/src/services/auth-interaction-service.test.js b/test/src/services/auth-interaction-service.test.js new file mode 100644 index 00000000..8cc139f0 --- /dev/null +++ b/test/src/services/auth-interaction-service.test.js @@ -0,0 +1,247 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const { generateSecret, generateSync } = require('otplib') +const Errors = require('../../../src/helpers/errors') +const embeddedOidc = require('../../../src/config/embedded-oidc') +const AuthInteractionService = require('../../../src/services/auth-interaction-service') +const { + snapshotOidcEnv, + DEFAULT_TEST_PASSWORD, + createEmbeddedAuthHarness, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') + +describe('Auth interaction service', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + def('interactionUid', () => 'interaction-test-uid') + + beforeEach(async () => { + AuthInteractionService.resetInteractionStateForTests() + await $harness + + $sandbox.stub(embeddedOidc, 'getEmbeddedProvider').returns({ + Interaction: { + find: $sandbox.stub().callsFake(async (uid) => { + if (uid !== $interactionUid) { + return undefined + } + return { + uid, + exp: Math.floor(Date.now() / 1000) + 600, + returnTo: 'https://controller.test/oidc/auth/test/resume', + save: $sandbox.stub().resolves(), + params: { + client_id: 'controller', + scope: 'openid profile email groups' + } + } + }) + }, + cookieName: () => 'interaction', + Grant: function Grant ({ accountId, clientId }) { + this.accountId = accountId + this.clientId = clientId + this.scopes = [] + this.addOIDCScope = (scope) => { + this.scopes.push(scope) + } + this.save = async () => 'grant-test-id' + } + }) + }) + + afterEach(() => { + AuthInteractionService.resetInteractionStateForTests() + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + describe('resolveNextStep', () => { + it('returns login when no verified user state exists', () => { + expect(AuthInteractionService.resolveNextStep(null, null)).to.equal('login') + }) + + it('returns mfa for admin with MFA enabled after login', () => { + const authContext = { + user: { id: 'user-1', mustChangePassword: false, isBootstrap: false }, + groups: [{ name: 'admin' }], + mfa: { enabled: true } + } + const state = { userId: 'user-1' } + expect(AuthInteractionService.resolveNextStep(authContext, state)).to.equal('mfa') + }) + + it('returns enroll for admin without MFA enrollment', () => { + const authContext = { + user: { id: 'user-1', mustChangePassword: false, isBootstrap: false }, + groups: [{ name: 'admin' }], + mfa: null + } + const state = { userId: 'user-1' } + expect(AuthInteractionService.resolveNextStep(authContext, state)).to.equal('enroll') + }) + + it('returns change-password for viewer with mustChangePassword after login', () => { + const authContext = { + user: { id: 'user-1', mustChangePassword: true, isBootstrap: false }, + groups: [{ name: 'viewer' }], + mfa: null + } + const state = { userId: 'user-1' } + expect(AuthInteractionService.resolveNextStep(authContext, state)).to.equal('change-password') + }) + + it('returns complete for viewer after forced password change', () => { + const authContext = { + user: { id: 'user-1', mustChangePassword: false, isBootstrap: false }, + groups: [{ name: 'viewer' }], + mfa: null + } + const state = { userId: 'user-1' } + expect(AuthInteractionService.resolveNextStep(authContext, state)).to.equal('complete') + }) + + it('returns complete for viewer after login when password change is not required', () => { + const authContext = { + user: { id: 'user-1', mustChangePassword: false, isBootstrap: false }, + groups: [{ name: 'viewer' }], + mfa: null + } + const state = { userId: 'user-1', passwordChanged: true } + expect(AuthInteractionService.resolveNextStep(authContext, state)).to.equal('complete') + }) + }) + + describe('forced password interaction flow', () => { + it('requires change-password before completion for temp password users', async () => { + const { store } = await $harness + await store.seedUser({ + email: 'temp@example.com', + groupNames: ['viewer'], + mustChangePassword: true + }) + + const loginResult = await AuthInteractionService.submitLogin($interactionUid, { + email: 'temp@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + expect(loginResult.step).to.equal('change-password') + + const changeResult = await AuthInteractionService.submitChangePassword($interactionUid, { + currentPassword: DEFAULT_TEST_PASSWORD, + newPassword: 'NewSecurePass456!' + }, false) + + expect(changeResult.step).to.equal('complete') + + const req = { headers: {} } + const res = {} + const completeResult = await AuthInteractionService.complete($interactionUid, req, res, false) + expect(completeResult.step).to.equal('complete') + }) + + it('rejects completion before forced password change', async () => { + const { store } = await $harness + await store.seedUser({ + email: 'temp@example.com', + groupNames: ['viewer'], + mustChangePassword: true + }) + + await AuthInteractionService.submitLogin($interactionUid, { + email: 'temp@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + try { + await AuthInteractionService.complete($interactionUid, { headers: {} }, {}, false) + expect.fail('expected completion to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.ValidationError) + } + }) + }) + + describe('interaction login flow', () => { + it('returns complete for viewer after login', async () => { + const { store } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const result = await AuthInteractionService.submitLogin($interactionUid, { + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + expect(result.step).to.equal('complete') + }) + + it('returns mfa step for admin with MFA enabled', async () => { + const { store } = await $harness + const totpSecret = generateSecret() + await store.seedUser({ + email: 'admin@example.com', + groupNames: ['admin'], + mfaEnabled: true, + totpSecret + }) + + const result = await AuthInteractionService.submitLogin($interactionUid, { + email: 'admin@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + expect(result.step).to.equal('mfa') + }) + + it('rejects invalid credentials with 401', async () => { + const { store } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + try { + await AuthInteractionService.submitLogin($interactionUid, { + email: 'viewer@example.com', + password: 'wrong-password' + }, false) + expect.fail('expected login to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.InvalidCredentialsError) + } + }) + + it('completes interaction and returns OAuth resume URL after MFA', async () => { + const { store } = await $harness + const totpSecret = generateSecret() + await store.seedUser({ + email: 'admin@example.com', + groupNames: ['admin'], + mfaEnabled: true, + totpSecret + }) + + await AuthInteractionService.submitLogin($interactionUid, { + email: 'admin@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const code = generateSync({ secret: totpSecret }) + const mfaResult = await AuthInteractionService.submitMfa($interactionUid, code, false) + expect(mfaResult.step).to.equal('complete') + + const req = { headers: {} } + const res = {} + const completeResult = await AuthInteractionService.complete($interactionUid, req, res, false) + + expect(completeResult.step).to.equal('complete') + expect(completeResult.redirectTo).to.equal('https://controller.test/oidc/auth/test/resume') + }) + }) +}) From fddd1d0a0898cc016ed52a9326a4f846d1a6eb99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 17:04:18 +0300 Subject: [PATCH 48/75] Enable OAuth BFF for embedded and external modes with unified URLs. Lift BFF to both auth modes, default viewer URL from public URL when unset, and document external OIDC client registration for operators. --- docs/external-oidc-client-setup.md | 210 +++++++++++++++++++ src/config/auth-urls.js | 25 +++ src/config/oidc.js | 65 +++++- src/services/auth-oauth-service.js | 106 ++++++---- test/src/config/auth-urls.test.js | 36 ++++ test/src/services/auth-oauth-service.test.js | 159 ++++++++++++++ 6 files changed, 556 insertions(+), 45 deletions(-) create mode 100644 docs/external-oidc-client-setup.md create mode 100644 src/config/auth-urls.js create mode 100644 test/src/config/auth-urls.test.js create mode 100644 test/src/services/auth-oauth-service.test.js diff --git a/docs/external-oidc-client-setup.md b/docs/external-oidc-client-setup.md new file mode 100644 index 00000000..1691b994 --- /dev/null +++ b/docs/external-oidc-client-setup.md @@ -0,0 +1,210 @@ +# External OIDC provider — minimum client setup for Controller + +**Audience:** Platform and IdP administrators +**Controller mode:** `AUTH_MODE=external` +**Applies to:** Any OIDC-compliant provider (Keycloak, Auth0, Okta, Azure AD, etc.) + +## Overview + +Controller uses one **confidential** OIDC client for browser and CLI authentication when `AUTH_MODE=external`. + +| Use case | Grant / flow | Controller endpoint | +|----------|--------------|---------------------| +| Browser (ECN Viewer) | Authorization code + PKCE S256 | `GET /api/v3/user/oauth/authorize` → IdP → `GET /api/v3/user/oauth/callback` | +| CLI (potctl) | Resource owner password (direct access) | `POST /api/v3/user/login` | +| Session refresh | Refresh token | `POST /api/v3/user/refresh` | +| Profile | Bearer access token (+ UserInfo) | `GET /api/v3/user/profile` | + +In external mode, access and refresh tokens are **issued by the IdP**. Controller validates access JWTs via the issuer JWKS and maps claims to RBAC. + +## Controller environment (minimum) + +| Variable | Required | Example | +|----------|----------|---------| +| `AUTH_MODE` | Yes | `external` | +| `OIDC_ISSUER_URL` | Yes | `https://auth.example.com/realms/myrealm` | +| `OIDC_CLIENT_ID` | Yes | `pot-controller` | +| `OIDC_CLIENT_SECRET` | Yes | Confidential client secret | +| `CONTROLLER_PUBLIC_URL` | Yes | `https://controller.example.com` | +| `VIEWER_URL` | Yes (browser login) | `https://viewer.example.com` | +| `AUTH_INSECURE_ALLOW_HTTP` | Development only | `true` when using `http://localhost:*` | + +## IdP client — required settings + +### Client type + +- OpenID Connect +- **Confidential client** (client authentication enabled; uses `OIDC_CLIENT_SECRET`) +- Public clients are not supported for the Controller OAuth BFF + +### Enabled grant types / flows + +| Flow | Required for | Notes | +|------|--------------|-------| +| Standard flow (authorization code) | Browser Sign in | Required | +| Direct access grants (ROPC) | CLI `POST /user/login` | Required if potctl uses password login against the IdP | +| Implicit flow | — | Off (not used) | + +### PKCE + +Controller always sends PKCE S256 on browser authorize (`code_challenge`, `code_challenge_method=S256`). + +| Provider | Setting | +|----------|---------| +| Keycloak | PKCE Method = **S256** (Capability config) | +| Others | Allow or require PKCE S256 on the authorization code flow | + +Disabling PKCE on the IdP is a development workaround only, not recommended for production. + +### Redirect URIs + +Register exactly: + +```text +{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback +``` + +Examples: + +- Production: `https://controller.example.com/api/v3/user/oauth/callback` +- Local: `http://localhost:51121/api/v3/user/oauth/callback` + +Avoid overly broad wildcards in production. + +### Web origins (CORS) + +If the Viewer calls the Controller API from the browser, allow: + +```text +{VIEWER_URL} +``` + +Example: `http://localhost:3000` + +## Scopes + +### Requested by Controller (browser OAuth BFF) + +Controller sends this scope string on authorize: + +```text +openid profile email groups offline_access +``` + +| Scope | Purpose | +|-------|---------| +| `openid` | OIDC baseline; `sub`, `id_token` | +| `profile` | `preferred_username`, display name | +| `email` | Email claim; identity linking | +| `groups` | RBAC group membership (see below) | +| `offline_access` | Refresh token on authorization code flow | + +### IdP client scope assignment + +Every requested scope must be assigned to the client as a **default** and/or **optional** client scope. + +| Scope | Typical assignment | +|-------|-------------------| +| `openid`, `profile`, `email`, `roles` | Default | +| `groups` | Optional (scope name must be **`groups`**, not `group`) | +| `offline_access` | Optional | + +**Common error:** `invalid_scope` when a scope is requested but not assigned to the client, or when the scope name is wrong (`group` vs `groups`). + +## RBAC / groups mapping + +Controller resolves RBAC **Group** subjects from the access token (in order): + +1. `resource_access[{OIDC_CLIENT_ID}].roles` — Keycloak client roles (recommended) +2. Top-level `roles` array (if present) +3. `groups` array — from the `groups` scope and a group mapper + +Group names are lowercased before RBAC lookup. IdP role and group names should align with Controller RBAC groups (for example `admin`, `viewer`). + +### Keycloak options + +**Option A — Client roles (simplest)** +Assign roles on the client matching `OIDC_CLIENT_ID`. The default `roles` scope adds `resource_access` to the token. + +**Option B — `groups` scope** +Create a realm client scope named exactly `groups`. Add a Group Membership or Realm Role mapper that emits a `groups` claim. + +### User identity (User subject) + +Controller reads the User subject from JWT claims (first match): + +`preferred_username` → `username` → `email` → `sub` + +Ensure at least one stable identifier is present for RBAC User bindings. + +## Issuer discovery (minimum metadata) + +The issuer at `OIDC_ISSUER_URL` must expose: + +| Endpoint | Used for | +|----------|----------| +| `authorization_endpoint` | Browser OAuth BFF | +| `token_endpoint` | Code exchange, ROPC, refresh | +| `jwks_uri` | Bearer JWT validation | +| `userinfo_endpoint` | `GET /user/profile` in external mode | +| `revocation_endpoint` | Optional; best-effort logout | + +## Keycloak checklist + +Example client: `pot-controller` + +| Setting | Value | +|---------|-------| +| Client authentication | On | +| Standard flow | On | +| Direct access grants | On (if CLI login is required) | +| PKCE Method | S256 | +| Valid redirect URIs | `{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback` | +| Web origins | `{VIEWER_URL}` | +| Client scopes | `openid`, `profile`, `email`, `roles` (default); `groups`, `offline_access` (optional) | + +### MFA and forced password change + +- **Browser:** enforced by the IdP during authorize (for example Keycloak required action `UPDATE_PASSWORD`) +- **CLI:** IdP password-grant policy applies +- Controller does not run embedded interaction UI in external mode + +## Verification + +### Browser + +1. Viewer Sign in → IdP login page (no `invalid_scope` or PKCE errors) +2. Callback → `{VIEWER_URL}/login#accessToken=...&refreshToken=...` +3. `GET /api/v3/user/profile` with `Authorization: Bearer ` → 200 + +### CLI + +```bash +curl -sS -X POST '{CONTROLLER_PUBLIC_URL}/api/v3/user/login' \ + -H 'Content-Type: application/json' \ + -d '{"email":"","password":"","totp":""}' +``` + +Expect `{ "accessToken", "refreshToken" }` (IdP tokens). + +### Refresh + +```bash +curl -sS -X POST '{CONTROLLER_PUBLIC_URL}/api/v3/user/refresh' \ + -H 'Content-Type: application/json' \ + -d '{"refreshToken":""}' +``` + +## Troubleshooting + +| Error | Likely cause | +|-------|----------------| +| `invalid_scope` | Missing `groups` or `offline_access` on the client; wrong scope name (`group` vs `groups`) | +| `Missing parameter: code_challenge_method` | PKCE required on IdP but not sent by Controller (upgrade Controller) | +| `redirect_uri` mismatch | Callback URL not registered exactly | +| Login works, no `refreshToken` in browser hash | `offline_access` not requested or not assigned on the IdP client | +| 403 on API routes | Token valid but RBAC groups/roles not mapped; check client roles or `groups` claim | + +## Related API documentation + +See `docs/swagger.yaml` for `/user/oauth/authorize`, `/user/oauth/callback`, `/user/login`, `/user/refresh`, and `/user/profile`. diff --git a/src/config/auth-urls.js b/src/config/auth-urls.js new file mode 100644 index 00000000..cac7d66e --- /dev/null +++ b/src/config/auth-urls.js @@ -0,0 +1,25 @@ +'use strict' + +const config = require('./index') + +function normalizeUrl (value) { + return String(value || '').replace(/\/$/, '') +} + +function getPublicUrl () { + return normalizeUrl(process.env.CONTROLLER_PUBLIC_URL || config.get('server.publicUrl') || '') +} + +function getViewerUrl () { + const explicit = process.env.VIEWER_URL || config.get('viewer.url') + if (explicit) { + return normalizeUrl(explicit) + } + return getPublicUrl() +} + +module.exports = { + getPublicUrl, + getViewerUrl, + normalizeUrl +} diff --git a/src/config/oidc.js b/src/config/oidc.js index f66f66e1..3e3cf11b 100644 --- a/src/config/oidc.js +++ b/src/config/oidc.js @@ -20,16 +20,17 @@ * Edgelet CP: no v3.8 env changes for mTLS; Plan 8.1 embedded issuer is separate. * See .cursor/rules/controller-oidc-handoff.mdc for v3.8 OIDC env contract. */ -const session = require('express-session') const oidcClient = require('openid-client') +const { allowInsecureRequests } = oidcClient const { createRemoteJWKSet, createLocalJWKSet, jwtVerify } = require('jose') const config = require('./index') const logger = require('../logger') +const { getPublicUrl: resolvePublicUrl } = require('./auth-urls') const { getActiveSigningMaterial, getPublicJwk } = require('./auth-jwks') let oidcInstance = null -let memoryStore = null let discoveryPromise = null +let embeddedOauthClientPromise = null let jwks = null let issuerString = null let configuredClientId = null @@ -49,7 +50,7 @@ function getAuthMode () { } function getPublicUrl () { - return process.env.CONTROLLER_PUBLIC_URL || config.get('server.publicUrl') || '' + return resolvePublicUrl() } function getExternalOidcSettings () { @@ -123,6 +124,16 @@ function buildKauthGrant (claims, rawToken) { } } +function getDiscoveryOptions () { + const allowHttp = process.env.AUTH_INSECURE_ALLOW_HTTP !== undefined + ? process.env.AUTH_INSECURE_ALLOW_HTTP === 'true' + : config.get('auth.insecureAllowHttp', false) === true + if (!allowHttp) { + return undefined + } + return { execute: [allowInsecureRequests] } +} + async function ensureEmbeddedJwks () { const { privateJwk } = await getActiveSigningMaterial() jwks = createLocalJWKSet({ keys: [getPublicJwk(privateJwk)] }) @@ -152,7 +163,9 @@ async function ensureDiscovery () { const configuration = await oidcClient.discovery( issuer, clientId, - clientSecret + clientSecret, + undefined, + getDiscoveryOptions() ) const metadata = configuration.serverMetadata() @@ -198,7 +211,6 @@ function initOidc () { } validateAuthConfig() - memoryStore = new session.MemoryStore() oidcInstance = createOidcFacade() logger.info(`OIDC initialized successfully (${getAuthMode()} mode)`) return oidcInstance @@ -239,6 +251,9 @@ function getOidcMiddleware () { } const { payload } = await jwtVerify(token, jwks, verifyOptions) + if (payload.token_use === 'refresh') { + throw new Error('Refresh token cannot be used as access token') + } req.kauth = buildKauthGrant(payload, token) return next() } catch (error) { @@ -252,20 +267,51 @@ function getOidcMiddleware () { } function getMemoryStore () { - return memoryStore + const { getAuthSessionStore } = require('./auth-session-store') + return getAuthSessionStore() } -async function getOidcConfiguration () { +async function ensureEmbeddedOauthClient () { + if (embeddedOauthClientPromise) { + return embeddedOauthClientPromise + } + + const { issuerUrl } = getOidcSettings() + const db = require('../data/models') + const { resolveConfidentialClientSecret } = require('./embedded-oidc-client-secret') + const { clientId, clientSecret } = await resolveConfidentialClientSecret(db) + + embeddedOauthClientPromise = oidcClient.discovery( + new URL(issuerUrl), + clientId, + clientSecret, + undefined, + getDiscoveryOptions() + ).catch((error) => { + embeddedOauthClientPromise = null + throw error + }) + + return embeddedOauthClientPromise +} + +async function getOauthClientConfiguration () { + if (getAuthMode() === 'embedded') { + await ensureEmbeddedJwks() + return ensureEmbeddedOauthClient() + } return ensureDiscovery() } function resetDiscoveryForTests () { discoveryPromise = null + embeddedOauthClientPromise = null jwks = null issuerString = null configuredClientId = null oidcInstance = null - memoryStore = null + const { resetAuthSessionStoreForTests } = require('./auth-session-store') + resetAuthSessionStoreForTests() } module.exports = { @@ -277,7 +323,8 @@ module.exports = { isAuthConfigured, validateAuthConfig, getOidcSettings, - getOidcConfiguration, + getOidcConfiguration: ensureDiscovery, + getOauthClientConfiguration, resetDiscoveryForTests, buildKauthGrant } diff --git a/src/services/auth-oauth-service.js b/src/services/auth-oauth-service.js index 750a88c6..91d6a89f 100644 --- a/src/services/auth-oauth-service.js +++ b/src/services/auth-oauth-service.js @@ -5,54 +5,46 @@ const { buildAuthorizationUrl, authorizationCodeGrant, randomState, - randomNonce + randomNonce, + randomPKCECodeVerifier, + calculatePKCECodeChallenge } = require('openid-client') -const config = require('../config') +const db = require('../data/models') const Errors = require('../helpers/errors') const logger = require('../logger') const { - getOidcConfiguration, - getAuthMode, - isAuthConfigured + getOauthClientConfiguration, + getAuthMode } = require('../config/oidc') +const { getPublicUrl, getViewerUrl } = require('../config/auth-urls') +const { getSessionStoreTtlMs } = require('../config/auth-session-store') +const AuthTokenService = require('./auth-token-service') const OAUTH_SESSION_KEY = 'controllerOauth' -const OAUTH_SESSION_TTL_MS = 10 * 60 * 1000 function ensureAuthConfigured () { + const { isAuthConfigured } = require('../config/oidc') if (!isAuthConfigured()) { throw new Error('Auth is not configured for this cluster. Please contact your administrator.') } } -function ensureExternalMode () { - if (getAuthMode() !== 'external') { - throw new Errors.NotImplementedError('OAuth BFF is only available in external auth mode') +function ensureOauthBffReady () { + if (!getViewerUrl()) { + throw new Errors.NotImplementedError('OAuth BFF requires CONTROLLER_PUBLIC_URL or VIEWER_URL to be configured') } } -function getPublicUrl () { - return (process.env.CONTROLLER_PUBLIC_URL || config.get('server.publicUrl') || '').replace(/\/$/, '') -} - function getRedirectUri () { return `${getPublicUrl()}/api/v3/user/oauth/callback` } -function getViewerUrl () { - const viewerUrl = process.env.VIEWER_URL || config.get('viewer.url') - if (!viewerUrl) { - return null - } - return String(viewerUrl).replace(/\/$/, '') -} - function ensureOauthSession (sessionData) { - if (!sessionData || !sessionData.state || !sessionData.nonce) { + if (!sessionData || !sessionData.state || !sessionData.nonce || !sessionData.codeVerifier) { throw new Errors.AuthenticationError('OAuth session expired or missing') } - if (Date.now() - sessionData.createdAt > OAUTH_SESSION_TTL_MS) { + if (Date.now() - sessionData.createdAt > getSessionStoreTtlMs()) { throw new Errors.AuthenticationError('OAuth session expired') } } @@ -101,25 +93,56 @@ function linkExternalUserByEmail (tokenResponse) { return email } +async function resolveEmbeddedUserFromTokenResponse (tokenResponse) { + if (!tokenResponse.id_token) { + throw new Errors.AuthenticationError('OAuth response missing id_token') + } + + const claims = decodeJwt(tokenResponse.id_token) + const userId = claims.sub + if (!userId) { + throw new Errors.AuthenticationError('OAuth response missing subject') + } + + const user = await db.AuthUser.findByPk(userId, { + include: [{ + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }] + }) + + if (!user || user.deletedAt) { + throw new Errors.AuthenticationError('OAuth user not found') + } + + return user +} + async function authorize (req) { ensureAuthConfigured() - ensureExternalMode() + ensureOauthBffReady() - const oidcConfig = await getOidcConfiguration() + const oidcConfig = await getOauthClientConfiguration() const state = randomState() const nonce = randomNonce() + const codeVerifier = randomPKCECodeVerifier() + const codeChallenge = await calculatePKCECodeChallenge(codeVerifier) req.session[OAUTH_SESSION_KEY] = { state, nonce, + codeVerifier, createdAt: Date.now() } const authorizationUrl = buildAuthorizationUrl(oidcConfig, { redirect_uri: getRedirectUri(), - scope: 'openid profile email', + scope: 'openid profile email groups offline_access', state, - nonce + nonce, + code_challenge: codeChallenge, + code_challenge_method: 'S256' }) return { redirectUrl: authorizationUrl.toString() } @@ -127,35 +150,46 @@ async function authorize (req) { async function callback (req) { ensureAuthConfigured() - ensureExternalMode() + ensureOauthBffReady() const sessionData = req.session[OAUTH_SESSION_KEY] ensureOauthSession(sessionData) delete req.session[OAUTH_SESSION_KEY] - const oidcConfig = await getOidcConfiguration() + const oidcConfig = await getOauthClientConfiguration() const currentUrl = new URL(`${getPublicUrl()}${req.originalUrl}`) let tokenResponse try { tokenResponse = await authorizationCodeGrant(oidcConfig, currentUrl, { expectedState: sessionData.state, - expectedNonce: sessionData.nonce + expectedNonce: sessionData.nonce, + pkceCodeVerifier: sessionData.codeVerifier }) } catch (error) { throw new Errors.AuthenticationError(error.message || 'OAuth authorization failed') } - linkExternalUserByEmail(tokenResponse) + const viewerUrl = getViewerUrl() - const tokens = { - accessToken: tokenResponse.access_token, - refreshToken: tokenResponse.refresh_token || null + if (getAuthMode() === 'embedded') { + const user = await resolveEmbeddedUserFromTokenResponse(tokenResponse) + const groupNames = (user.groups || []).map((group) => group.name) + const tokens = await AuthTokenService.issueTokenPair(user, groupNames) + return { + tokens, + viewerUrl + } } + linkExternalUserByEmail(tokenResponse) + return { - tokens, - viewerUrl: getViewerUrl() + tokens: { + accessToken: tokenResponse.access_token, + refreshToken: tokenResponse.refresh_token || null + }, + viewerUrl } } diff --git a/test/src/config/auth-urls.test.js b/test/src/config/auth-urls.test.js new file mode 100644 index 00000000..d0909d49 --- /dev/null +++ b/test/src/config/auth-urls.test.js @@ -0,0 +1,36 @@ +'use strict' + +const { expect } = require('chai') +const { + snapshotOidcEnv, + restoreOidcEnv, + applyOidcEnv +} = require('../../support/oidc-test-helpers') + +describe('auth-urls', () => { + def('envSnapshot', () => snapshotOidcEnv()) + + afterEach(() => { + restoreOidcEnv($envSnapshot) + delete process.env.VIEWER_URL + delete process.env.CONTROLLER_PUBLIC_URL + delete require.cache[require.resolve('../../../src/config/auth-urls')] + }) + + it('uses CONTROLLER_PUBLIC_URL when VIEWER_URL is unset', () => { + process.env.CONTROLLER_PUBLIC_URL = 'https://controller.example.com/' + delete process.env.VIEWER_URL + + const { getPublicUrl, getViewerUrl } = require('../../../src/config/auth-urls') + expect(getPublicUrl()).to.equal('https://controller.example.com') + expect(getViewerUrl()).to.equal('https://controller.example.com') + }) + + it('prefers explicit VIEWER_URL over CONTROLLER_PUBLIC_URL', () => { + process.env.CONTROLLER_PUBLIC_URL = 'https://controller.example.com' + process.env.VIEWER_URL = 'https://viewer.example.com/' + + const { getViewerUrl } = require('../../../src/config/auth-urls') + expect(getViewerUrl()).to.equal('https://viewer.example.com') + }) +}) diff --git a/test/src/services/auth-oauth-service.test.js b/test/src/services/auth-oauth-service.test.js new file mode 100644 index 00000000..a7029367 --- /dev/null +++ b/test/src/services/auth-oauth-service.test.js @@ -0,0 +1,159 @@ +'use strict' + +const { expect } = require('chai') +const { decodeJwt, SignJWT } = require('jose') +const sinon = require('sinon') +const { Configuration } = require('openid-client') +const Module = require('module') +const { + snapshotOidcEnv, + createEmbeddedAuthHarness, + teardownEmbeddedAuth, + applyExternalEnv +} = require('../../support/embedded-auth-harness') + +function createTestOidcConfiguration () { + return new Configuration( + { + issuer: 'https://idp.example.com/realms/test', + authorization_endpoint: 'https://idp.example.com/realms/test/protocol/openid-connect/auth', + token_endpoint: 'https://idp.example.com/realms/test/protocol/openid-connect/token', + jwks_uri: 'https://idp.example.com/realms/test/protocol/openid-connect/certs' + }, + 'controller', + { client_secret: 'external-test-secret' } + ) +} + +describe('Auth OAuth service', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox, { + env: { VIEWER_URL: 'http://viewer.test' } + })) + def('oidcConfig', () => createTestOidcConfiguration()) + + beforeEach(async () => { + await $harness + process.env.VIEWER_URL = 'http://viewer.test' + delete require.cache[require.resolve('../../../src/services/auth-oauth-service')] + const oidcModule = require('../../../src/config/oidc') + $sandbox.stub(oidcModule, 'getOauthClientConfiguration').resolves($oidcConfig) + }) + + afterEach(() => { + delete require.cache[require.resolve('../../../src/services/auth-oauth-service')] + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + it('includes PKCE parameters in authorize redirect and stores codeVerifier in session', async () => { + const AuthOauthService = require('../../../src/services/auth-oauth-service') + const req = { session: {} } + + const { redirectUrl } = await AuthOauthService.authorize(req) + const url = new URL(redirectUrl) + + expect(url.searchParams.get('code_challenge_method')).to.equal('S256') + expect(url.searchParams.get('code_challenge')).to.be.a('string').and.not.be.empty + expect(url.searchParams.get('scope')).to.equal('openid profile email groups offline_access') + expect(req.session.controllerOauth.codeVerifier).to.be.a('string').and.not.be.empty + expect(req.session.controllerOauth.state).to.be.a('string').and.not.be.empty + expect(req.session.controllerOauth.nonce).to.be.a('string').and.not.be.empty + }) + + it('passes pkceCodeVerifier to authorizationCodeGrant on callback', async () => { + applyExternalEnv({}) + process.env.VIEWER_URL = 'http://viewer.test' + + const grantStub = $sandbox.stub().resolves({ + access_token: 'external-access-token', + refresh_token: 'external-refresh-token' + }) + + const authOauthServicePath = require.resolve('../../../src/services/auth-oauth-service') + delete require.cache[authOauthServicePath] + + const originalRequire = Module.prototype.require + Module.prototype.require = function (request, ...args) { + const loaded = originalRequire.call(this, request, ...args) + if ( + request === 'openid-client' && + typeof this.filename === 'string' && + this.filename.endsWith('auth-oauth-service.js') + ) { + return { ...loaded, authorizationCodeGrant: grantStub } + } + return loaded + } + + let AuthOauthService + try { + AuthOauthService = require('../../../src/services/auth-oauth-service') + } finally { + Module.prototype.require = originalRequire + } + + const req = { + session: { + controllerOauth: { + state: 'test-state', + nonce: 'test-nonce', + codeVerifier: 'test-pkce-verifier', + createdAt: Date.now() + } + }, + originalUrl: '/api/v3/user/oauth/callback?code=auth-code&state=test-state' + } + + const result = await AuthOauthService.callback(req) + + expect(grantStub).to.have.been.calledOnce + expect(grantStub.firstCall.args[2].pkceCodeVerifier).to.equal('test-pkce-verifier') + expect(grantStub.firstCall.args[2].expectedState).to.equal('test-state') + expect(grantStub.firstCall.args[2].expectedNonce).to.equal('test-nonce') + expect(result.tokens.accessToken).to.equal('external-access-token') + expect(result.tokens.refreshToken).to.equal('external-refresh-token') + expect(result.viewerUrl).to.equal('http://viewer.test') + expect(req.session.controllerOauth).to.be.undefined + }) + + it('resolves embedded oauth users from id_token and issues Controller JWT pair', async () => { + const { store, signing } = await $harness + const AuthOauthService = require('../../../src/services/auth-oauth-service') + const AuthTokenService = require('../../../src/services/auth-token-service') + + const { user } = await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const idToken = await new SignJWT({ email: 'viewer@example.com' }) + .setProtectedHeader({ alg: 'RS256', kid: signing.privateJwk.kid }) + .setSubject(user.id) + .setIssuer(`${process.env.CONTROLLER_PUBLIC_URL}/oidc`) + .setAudience('controller') + .setIssuedAt() + .setExpirationTime('5m') + .sign(signing.privateKey) + + expect(decodeJwt(idToken).sub).to.equal(user.id) + + const resolvedUser = await AuthOauthService.resolveEmbeddedUserFromTokenResponse({ + id_token: idToken + }) + const tokens = await AuthTokenService.issueTokenPair(resolvedUser, ['viewer']) + + expect(resolvedUser.id).to.equal(user.id) + expect(tokens.accessToken.split('.')).to.have.length(3) + expect(tokens.refreshToken.split('.')).to.have.length(3) + + const accessClaims = decodeJwt(tokens.accessToken) + const refreshClaims = decodeJwt(tokens.refreshToken) + + expect(accessClaims.sub).to.equal(user.id) + expect(accessClaims.token_use).to.equal('access') + expect(refreshClaims.token_use).to.equal('refresh') + expect(refreshClaims.sub).to.equal(user.id) + }) +}) From 3968a05f50a231d3f8b218ef00ae2e803a121442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 17:04:23 +0300 Subject: [PATCH 49/75] Add built-in rate limiting for authentication endpoints. Throttle login, OAuth, and interaction traffic per client IP with configurable window and request limits. --- src/middlewares/auth-rate-limit-middleware.js | 108 ++++++++++++++++ .../auth-rate-limit-middleware.test.js | 119 ++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 src/middlewares/auth-rate-limit-middleware.js create mode 100644 test/src/middlewares/auth-rate-limit-middleware.test.js diff --git a/src/middlewares/auth-rate-limit-middleware.js b/src/middlewares/auth-rate-limit-middleware.js new file mode 100644 index 00000000..b8e37b3c --- /dev/null +++ b/src/middlewares/auth-rate-limit-middleware.js @@ -0,0 +1,108 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const config = require('../config') +const constants = require('../helpers/constants') +const logger = require('../logger') + +const EXACT_PATHS = new Set([ + '/api/v3/user/login', + '/api/v3/user/oauth/authorize', + '/api/v3/user/oauth/callback', + '/api/v3/user/change-password', + '/api/v3/user/mfa/enroll', + '/api/v3/user/mfa/confirm' +]) + +const INTERACTION_POST_PREFIX = '/api/v3/user/interaction/' + +const rateLimits = new Map() + +function getRateLimitConfig () { + return { + enabled: config.get('auth.rateLimit.enabled', true), + maxRequests: config.get('auth.rateLimit.maxRequestsPerWindow', 60), + windowMs: config.get('auth.rateLimit.windowMs', 60000) + } +} + +function getClientIp (req) { + return req.ip || (req.socket && req.socket.remoteAddress) || 'unknown' +} + +function isProtectedAuthRoute (method, path) { + const normalizedMethod = (method || '').toUpperCase() + const normalizedPath = path || '' + + if (EXACT_PATHS.has(normalizedPath)) { + return true + } + + return normalizedMethod === 'POST' && + normalizedPath.startsWith(INTERACTION_POST_PREFIX) && + normalizedPath.length > INTERACTION_POST_PREFIX.length +} + +function buildRateLimitResponse (resetTime) { + return { + name: 'RateLimitExceededError', + message: 'Too many authentication requests from this IP address' + } +} + +function authRateLimitMiddleware (req, res, next) { + const { enabled, maxRequests, windowMs } = getRateLimitConfig() + if (!enabled) { + return next() + } + + const path = req.path || (req.url && req.url.split('?')[0]) || '' + if (!isProtectedAuthRoute(req.method, path)) { + return next() + } + + const clientIp = getClientIp(req) + const now = Date.now() + let bucket = rateLimits.get(clientIp) + + if (!bucket || now > bucket.resetTime) { + bucket = { count: 0, resetTime: now + windowMs } + } + + if (bucket.count >= maxRequests) { + const retryAfterSeconds = Math.max(1, Math.ceil((bucket.resetTime - now) / 1000)) + logger.warn({ + msg: 'Auth rate limit exceeded', + clientIp, + method: req.method, + path + }) + res.set('Retry-After', String(retryAfterSeconds)) + return res.status(constants.HTTP_CODE_TOO_MANY_REQUESTS).json(buildRateLimitResponse(bucket.resetTime)) + } + + bucket.count += 1 + rateLimits.set(clientIp, bucket) + return next() +} + +function resetAuthRateLimitStore () { + rateLimits.clear() +} + +module.exports = { + authRateLimitMiddleware, + resetAuthRateLimitStore, + isProtectedAuthRoute, + getRateLimitConfig +} diff --git a/test/src/middlewares/auth-rate-limit-middleware.test.js b/test/src/middlewares/auth-rate-limit-middleware.test.js new file mode 100644 index 00000000..bc883c5e --- /dev/null +++ b/test/src/middlewares/auth-rate-limit-middleware.test.js @@ -0,0 +1,119 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const config = require('../../../src/config') +const constants = require('../../../src/helpers/constants') +const { + authRateLimitMiddleware, + resetAuthRateLimitStore, + isProtectedAuthRoute +} = require('../../../src/middlewares/auth-rate-limit-middleware') + +describe('auth-rate-limit-middleware', () => { + def('sandbox', () => sinon.createSandbox()) + def('next', () => sinon.spy()) + def('res', () => ({ + status: sinon.stub().returnsThis(), + json: sinon.stub().returnsThis(), + set: sinon.stub().returnsThis() + })) + + beforeEach(() => { + resetAuthRateLimitStore() + $sandbox.stub(config, 'get').callsFake((key, defaultValue) => { + const overrides = { + 'auth.rateLimit.enabled': true, + 'auth.rateLimit.maxRequestsPerWindow': 2, + 'auth.rateLimit.windowMs': 60000 + } + return overrides[key] !== undefined ? overrides[key] : defaultValue + }) + }) + + afterEach(() => { + $sandbox.restore() + resetAuthRateLimitStore() + }) + + describe('isProtectedAuthRoute', () => { + it('matches exact auth endpoints', () => { + expect(isProtectedAuthRoute('POST', '/api/v3/user/login')).to.equal(true) + expect(isProtectedAuthRoute('GET', '/api/v3/user/oauth/authorize')).to.equal(true) + expect(isProtectedAuthRoute('POST', '/api/v3/user/change-password')).to.equal(true) + }) + + it('matches POST interaction endpoints only', () => { + expect(isProtectedAuthRoute('POST', '/api/v3/user/interaction/abc/login')).to.equal(true) + expect(isProtectedAuthRoute('GET', '/api/v3/user/interaction/abc')).to.equal(false) + }) + + it('ignores unrelated routes', () => { + expect(isProtectedAuthRoute('POST', '/api/v3/user/refresh')).to.equal(false) + expect(isProtectedAuthRoute('GET', '/api/v3/user/profile')).to.equal(false) + }) + }) + + describe('authRateLimitMiddleware', () => { + def('req', () => ({ + method: 'POST', + path: '/api/v3/user/login', + ip: '203.0.113.10' + })) + + it('passes through non-auth routes', () => { + authRateLimitMiddleware({ method: 'POST', path: '/api/v3/user/refresh', ip: '203.0.113.10' }, $res, $next) + + expect($next).to.have.been.calledOnce + expect($res.status).to.not.have.been.called + }) + + it('allows requests under the configured limit', () => { + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + + expect($next).to.have.been.calledTwice + expect($res.status).to.not.have.been.called + }) + + it('returns 429 with a consistent error body when the limit is exceeded', () => { + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + + expect($next).to.have.been.calledTwice + expect($res.status).to.have.been.calledOnceWith(constants.HTTP_CODE_TOO_MANY_REQUESTS) + expect($res.set).to.have.been.calledWith('Retry-After', sinon.match.string) + expect($res.json).to.have.been.calledOnceWith({ + name: 'RateLimitExceededError', + message: 'Too many authentication requests from this IP address' + }) + }) + + it('tracks limits per client IP', () => { + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + + const otherReq = { ...$req, ip: '203.0.113.11' } + authRateLimitMiddleware(otherReq, $res, $next) + + expect($next).to.have.been.calledThrice + }) + + it('skips limiting when disabled in config', () => { + config.get.restore() + $sandbox.stub(config, 'get').callsFake((key, defaultValue) => { + if (key === 'auth.rateLimit.enabled') { + return false + } + return defaultValue + }) + + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + + expect($next).to.have.been.calledThrice + expect($res.status).to.not.have.been.called + }) + }) +}) From 97a253c8a4a18c79926cc9b9d734f61dd9fee136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 17:04:27 +0300 Subject: [PATCH 50/75] Add shared BFF session store, token TTL overrides, and OIDC client secret sync. Auto-select database session store for mysql/postgres, persist encrypted BFF session secrets, reconcile embedded client secrets from env into the database, and support runtime access and refresh token TTL configuration. --- src/config/auth-oidc-ttl.js | 82 ++++++++ src/config/auth-policy-defaults.js | 20 ++ src/config/auth-session-store.js | 196 ++++++++++++++++++ src/config/auth-token-ttl.js | 45 ++++ src/config/embedded-oidc-client-secret.js | 108 ++++++++++ src/services/auth-policy-service.js | 21 +- test/src/config/auth-oidc-ttl.test.js | 92 ++++++++ test/src/config/auth-session-store.test.js | 161 ++++++++++++++ test/src/config/auth-token-ttl.test.js | 38 ++++ .../embedded-oidc-client-secret.test.js | 146 +++++++++++++ 10 files changed, 892 insertions(+), 17 deletions(-) create mode 100644 src/config/auth-oidc-ttl.js create mode 100644 src/config/auth-policy-defaults.js create mode 100644 src/config/auth-session-store.js create mode 100644 src/config/auth-token-ttl.js create mode 100644 src/config/embedded-oidc-client-secret.js create mode 100644 test/src/config/auth-oidc-ttl.test.js create mode 100644 test/src/config/auth-session-store.test.js create mode 100644 test/src/config/auth-token-ttl.test.js create mode 100644 test/src/config/embedded-oidc-client-secret.test.js diff --git a/src/config/auth-oidc-ttl.js b/src/config/auth-oidc-ttl.js new file mode 100644 index 00000000..b97df454 --- /dev/null +++ b/src/config/auth-oidc-ttl.js @@ -0,0 +1,82 @@ +'use strict' + +const config = require('./index') +const { DEFAULT_POLICY } = require('./auth-policy-defaults') +const { getSessionStoreTtlMs } = require('./auth-session-store') +const { applyTokenTtlOverrides } = require('./auth-token-ttl') + +function parsePositiveSeconds (value, keyName) { + if (value === undefined || value === null || value === '') { + return null + } + const parsed = Number(value) + if (!Number.isFinite(parsed) || parsed <= 0) { + throw new Error(`${keyName} must be a positive number`) + } + return Math.floor(parsed) +} + +function getConfiguredSeconds (configPath, envKey) { + const fromEnv = parsePositiveSeconds(process.env[envKey], envKey) + if (fromEnv !== null) { + return fromEnv + } + return parsePositiveSeconds(config.get(configPath), configPath) +} + +function resolveOauthFlowTtlSeconds () { + const override = getConfiguredSeconds('auth.oidcTtl.interactionTtlSeconds', 'AUTH_OIDC_INTERACTION_TTL_SECONDS') + if (override !== null) { + return override + } + return Math.max(1, Math.floor(getSessionStoreTtlMs() / 1000)) +} + +function resolveGrantTtlSeconds () { + const override = getConfiguredSeconds('auth.oidcTtl.grantTtlSeconds', 'AUTH_OIDC_GRANT_TTL_SECONDS') + if (override !== null) { + return override + } + return resolveOauthFlowTtlSeconds() +} + +function resolveSessionTtlSeconds (policy) { + const override = getConfiguredSeconds('auth.oidcTtl.sessionTtlSeconds', 'AUTH_OIDC_SESSION_TTL_SECONDS') + if (override !== null) { + return override + } + return (policy && policy.refreshTokenTtlSeconds) || DEFAULT_POLICY.refreshTokenTtlSeconds +} + +function resolveIdTokenTtlSeconds (policy) { + const override = getConfiguredSeconds('auth.oidcTtl.idTokenTtlSeconds', 'AUTH_OIDC_ID_TOKEN_TTL_SECONDS') + if (override !== null) { + return override + } + return (policy && policy.accessTokenTtlSeconds) || DEFAULT_POLICY.accessTokenTtlSeconds +} + +function resolveOidcProviderTtls (policy) { + const normalizedPolicy = applyTokenTtlOverrides(policy || {}) + return { + accessTokenTtlSeconds: normalizedPolicy.accessTokenTtlSeconds, + refreshTokenTtlSeconds: normalizedPolicy.refreshTokenTtlSeconds, + idTokenTtlSeconds: resolveIdTokenTtlSeconds(normalizedPolicy), + interactionTtlSeconds: resolveOauthFlowTtlSeconds(), + grantTtlSeconds: resolveGrantTtlSeconds(), + sessionTtlSeconds: resolveSessionTtlSeconds(normalizedPolicy) + } +} + +async function loadOidcProviderTtls (db) { + const policy = db.AuthPolicy ? await db.AuthPolicy.findByPk(1) : null + const plainPolicy = policy + ? (typeof policy.get === 'function' ? policy.get({ plain: true }) : policy) + : null + return resolveOidcProviderTtls(plainPolicy) +} + +module.exports = { + resolveOidcProviderTtls, + loadOidcProviderTtls +} diff --git a/src/config/auth-policy-defaults.js b/src/config/auth-policy-defaults.js new file mode 100644 index 00000000..d8d84507 --- /dev/null +++ b/src/config/auth-policy-defaults.js @@ -0,0 +1,20 @@ +'use strict' + +const DEFAULT_POLICY = { + minPasswordLength: 12, + requireUppercase: true, + requireLowercase: true, + requireDigit: true, + passwordMaxAgeDays: 0, + passwordHistoryCount: 5, + maxFailedAttempts: 5, + lockoutDurationMinutes: 15, + accessTokenTtlSeconds: 900, + refreshTokenTtlSeconds: 3600, + refreshRotation: true, + maxConcurrentSessions: null +} + +module.exports = { + DEFAULT_POLICY +} diff --git a/src/config/auth-session-store.js b/src/config/auth-session-store.js new file mode 100644 index 00000000..0307f61e --- /dev/null +++ b/src/config/auth-session-store.js @@ -0,0 +1,196 @@ +'use strict' + +const crypto = require('crypto') +const session = require('express-session') +const config = require('./index') +const logger = require('../logger') +const secretHelper = require('../helpers/secret-helper') + +const VALID_TYPES = ['memory', 'database'] +const DEFAULT_TTL_MS = 10 * 60 * 1000 +const SESSION_SECRET_NAME = 'auth-bff-session-secret' + +let storeInstance = null +let cachedSessionSecret = null + +function getDatabaseProvider () { + return process.env.DB_PROVIDER || config.get('database.provider', 'sqlite') +} + +function getSessionStoreType () { + const explicit = process.env.AUTH_SESSION_STORE_TYPE || config.get('auth.sessionStore.type') + if (explicit) { + if (!VALID_TYPES.includes(explicit)) { + throw new Error(`Invalid auth.sessionStore.type "${explicit}". Must be memory or database`) + } + return explicit + } + + const provider = getDatabaseProvider() + if (provider === 'mysql' || provider === 'postgres') { + return 'database' + } + return 'memory' +} + +function getSessionStoreTtlMs () { + const envValue = process.env.AUTH_SESSION_STORE_TTL_MS + if (envValue !== undefined && envValue !== null && envValue !== '') { + const parsed = Number(envValue) + if (!Number.isFinite(parsed) || parsed <= 0) { + throw new Error('AUTH_SESSION_STORE_TTL_MS must be a positive number') + } + return parsed + } + + const configured = config.get('auth.sessionStore.ttlMs', DEFAULT_TTL_MS) + const parsed = Number(configured) + if (!Number.isFinite(parsed) || parsed <= 0) { + return DEFAULT_TTL_MS + } + return parsed +} + +function getSessionStoreConfig () { + return { + type: getSessionStoreType(), + ttlMs: getSessionStoreTtlMs() + } +} + +function getConfiguredSessionSecret () { + const envSecret = process.env.AUTH_SESSION_SECRET + if (envSecret) { + return envSecret + } + + const configured = config.get('auth.sessionStore.secret') + if (configured) { + return configured + } + + return null +} + +async function resolveStoredSessionSecret (secretRef) { + if (!secretRef) { + return null + } + + try { + const data = await secretHelper.decryptSecret(secretRef, SESSION_SECRET_NAME, 'auth-session') + return data.secret || data.value || null + } catch (error) { + return null + } +} + +async function persistSessionSecret (db, secret) { + const secretRef = await secretHelper.encryptSecret({ secret }, SESSION_SECRET_NAME, 'auth-session') + let meta = await db.AuthBootstrapMeta.findByPk(1) + if (!meta) { + meta = await db.AuthBootstrapMeta.create({ id: 1, sessionSecretRef: secretRef }) + return meta + } + + await meta.update({ sessionSecretRef: secretRef }) + return meta +} + +function generateSessionSecret () { + return crypto.randomBytes(32).toString('base64url') +} + +async function resolveSessionSecret () { + const configured = getConfiguredSessionSecret() + if (configured) { + cachedSessionSecret = configured + return configured + } + + const db = require('../data/models') + if (!db.AuthBootstrapMeta) { + throw new Error('AuthBootstrapMeta model is required to resolve auth session secret') + } + + let meta = await db.AuthBootstrapMeta.findByPk(1) + if (!meta) { + meta = await db.AuthBootstrapMeta.create({ id: 1 }) + } + + const storedSecret = await resolveStoredSessionSecret(meta.sessionSecretRef) + if (storedSecret) { + cachedSessionSecret = storedSecret + return storedSecret + } + + const generated = generateSessionSecret() + await persistSessionSecret(db, generated) + logger.info('Generated auth BFF session secret and persisted to database') + cachedSessionSecret = generated + return generated +} + +function getSessionSecret () { + if (!cachedSessionSecret) { + throw new Error('Auth session secret is not initialized. Call resolveSessionSecret() during startup.') + } + return cachedSessionSecret +} + +function createSessionStore () { + const { type, ttlMs } = getSessionStoreConfig() + + if (type === 'memory') { + return new session.MemoryStore() + } + + const db = require('../data/models') + if (!db.AuthBffSession) { + throw new Error('Database session store requires AuthBffSession model') + } + + const SequelizeSessionStore = require('../data/stores/sequelize-session-store') + return new SequelizeSessionStore({ + model: db.AuthBffSession, + ttlMs + }) +} + +function initAuthSessionStore () { + if (!storeInstance) { + storeInstance = createSessionStore() + logger.info(`Auth BFF session store initialized (${getSessionStoreConfig().type})`) + } + return storeInstance +} + +function getAuthSessionStore () { + return storeInstance || initAuthSessionStore() +} + +function isSharedSessionStore () { + return getSessionStoreType() !== 'memory' +} + +function resetAuthSessionStoreForTests () { + storeInstance = null + cachedSessionSecret = null +} + +function setSessionSecretForTests (secret) { + cachedSessionSecret = secret +} + +module.exports = { + getSessionStoreConfig, + getSessionStoreTtlMs, + getSessionSecret, + resolveSessionSecret, + initAuthSessionStore, + getAuthSessionStore, + isSharedSessionStore, + resetAuthSessionStoreForTests, + setSessionSecretForTests, + getMemoryStore: getAuthSessionStore +} diff --git a/src/config/auth-token-ttl.js b/src/config/auth-token-ttl.js new file mode 100644 index 00000000..3b1687f1 --- /dev/null +++ b/src/config/auth-token-ttl.js @@ -0,0 +1,45 @@ +'use strict' + +const config = require('./index') +const { DEFAULT_POLICY } = require('./auth-policy-defaults') + +function parsePositiveSeconds (value, keyName) { + if (value === undefined || value === null || value === '') { + return null + } + const parsed = Number(value) + if (!Number.isFinite(parsed) || parsed <= 0) { + throw new Error(`${keyName} must be a positive number`) + } + return Math.floor(parsed) +} + +function getConfiguredSeconds (configPath, envKey) { + const fromEnv = parsePositiveSeconds(process.env[envKey], envKey) + if (fromEnv !== null) { + return fromEnv + } + return parsePositiveSeconds(config.get(configPath), configPath) +} + +function applyTokenTtlOverrides (policy) { + const normalized = policy || {} + const accessOverride = getConfiguredSeconds('auth.tokenTtl.accessTokenTtlSeconds', 'AUTH_ACCESS_TOKEN_TTL_SECONDS') + const refreshOverride = getConfiguredSeconds('auth.tokenTtl.refreshTokenTtlSeconds', 'AUTH_REFRESH_TOKEN_TTL_SECONDS') + + return { + ...DEFAULT_POLICY, + ...normalized, + accessTokenTtlSeconds: accessOverride !== null + ? accessOverride + : (normalized.accessTokenTtlSeconds || DEFAULT_POLICY.accessTokenTtlSeconds), + refreshTokenTtlSeconds: refreshOverride !== null + ? refreshOverride + : (normalized.refreshTokenTtlSeconds || DEFAULT_POLICY.refreshTokenTtlSeconds) + } +} + +module.exports = { + applyTokenTtlOverrides, + getConfiguredSeconds +} diff --git a/src/config/embedded-oidc-client-secret.js b/src/config/embedded-oidc-client-secret.js new file mode 100644 index 00000000..94d3aa8a --- /dev/null +++ b/src/config/embedded-oidc-client-secret.js @@ -0,0 +1,108 @@ +'use strict' + +const crypto = require('crypto') +const config = require('./index') +const logger = require('../logger') +const secretHelper = require('../helpers/secret-helper') + +const CONTROLLER_CLIENT_ID = 'controller' + +function getConfidentialClientId () { + return process.env.OIDC_CLIENT_ID || config.get('auth.client.id') || CONTROLLER_CLIENT_ID +} + +function getEnvClientSecret () { + const secret = process.env.OIDC_CLIENT_SECRET || config.get('auth.client.secret') || '' + return secret || null +} + +function generateClientSecret () { + return crypto.randomBytes(32).toString('base64url') +} + +async function resolveStoredSecret (secretRef, secretName, secretType) { + if (!secretRef) { + return null + } + + if (secretHelper.isVaultReference(secretRef)) { + const data = await secretHelper.decryptSecret(secretRef, secretName, secretType) + return data.secret || data.client_secret || data.value || null + } + + try { + const data = await secretHelper.decryptSecret(secretRef, secretName, secretType) + return data.secret || data.client_secret || data.value || null + } catch (error) { + return secretRef + } +} + +async function persistClientSecret (db, clientId, secret) { + const secretRef = await secretHelper.encryptSecret({ secret }, `oidc-client-${clientId}`, 'oidc-client') + const existing = await db.AuthOidcClient.findOne({ where: { clientId } }) + if (existing) { + await existing.update({ secretRef }) + return existing + } + + return db.AuthOidcClient.create({ + clientId, + secretRef, + clientType: 'confidential' + }) +} + +/** + * Resolve the embedded confidential OIDC client secret (env → DB → optional generate). + * Env wins at runtime; when env differs from DB, DB is reconciled on startup (encrypted). + */ +async function resolveConfidentialClientSecret (db, { createIfMissing = false } = {}) { + const clientId = getConfidentialClientId() + const envSecret = getEnvClientSecret() + + if (!db || !db.AuthOidcClient) { + if (envSecret) { + return { clientId, clientSecret: envSecret } + } + throw new Error('AuthOidcClient model is required to resolve embedded OIDC client secret') + } + + const clientRow = await db.AuthOidcClient.findOne({ where: { clientId } }) + let dbSecret = null + if (clientRow && clientRow.secretRef) { + dbSecret = await resolveStoredSecret(clientRow.secretRef, `oidc-client-${clientId}`, 'oidc-client') + } + + if (envSecret) { + if (!dbSecret || dbSecret !== envSecret) { + await persistClientSecret(db, clientId, envSecret) + if (dbSecret && dbSecret !== envSecret) { + logger.info(`Reconciled embedded OIDC client secret for "${clientId}" from env`) + } + } + return { clientId, clientSecret: envSecret } + } + + if (dbSecret) { + return { clientId, clientSecret: dbSecret } + } + + if (createIfMissing) { + const secret = generateClientSecret() + await persistClientSecret(db, clientId, secret) + logger.info(`Generated embedded OIDC client secret for "${clientId}"`) + return { clientId, clientSecret: secret } + } + + throw new Error( + `Embedded OIDC client secret for "${clientId}" is not available. ` + + 'Ensure embedded issuer is initialized or set OIDC_CLIENT_SECRET.' + ) +} + +module.exports = { + CONTROLLER_CLIENT_ID, + getConfidentialClientId, + resolveConfidentialClientSecret +} diff --git a/src/services/auth-policy-service.js b/src/services/auth-policy-service.js index 52c3da96..bcb88162 100644 --- a/src/services/auth-policy-service.js +++ b/src/services/auth-policy-service.js @@ -2,28 +2,15 @@ const db = require('../data/models') const { withTransaction } = require('../helpers/app-helper') - -const DEFAULT_POLICY = { - minPasswordLength: 12, - requireUppercase: true, - requireLowercase: true, - requireDigit: true, - passwordMaxAgeDays: 0, - passwordHistoryCount: 5, - maxFailedAttempts: 5, - lockoutDurationMinutes: 15, - accessTokenTtlSeconds: 900, - refreshTokenTtlSeconds: 604800, - refreshRotation: true, - maxConcurrentSessions: null -} +const { applyTokenTtlOverrides } = require('../config/auth-token-ttl') +const { DEFAULT_POLICY } = require('../config/auth-policy-defaults') async function getPolicy (transaction) { const policy = await db.AuthPolicy.findByPk(1, withTransaction(transaction)) if (!policy) { - return { ...DEFAULT_POLICY } + return applyTokenTtlOverrides({ ...DEFAULT_POLICY }) } - return policy.get({ plain: true }) + return applyTokenTtlOverrides(policy.get({ plain: true })) } function isAccountLocked (user, policy) { diff --git a/test/src/config/auth-oidc-ttl.test.js b/test/src/config/auth-oidc-ttl.test.js new file mode 100644 index 00000000..d511ca48 --- /dev/null +++ b/test/src/config/auth-oidc-ttl.test.js @@ -0,0 +1,92 @@ +'use strict' + +const { expect } = require('chai') +const { + snapshotOidcEnv, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') + +describe('Auth OIDC TTL resolution', () => { + def('envSnapshot', () => snapshotOidcEnv()) + + afterEach(() => { + delete process.env.AUTH_SESSION_STORE_TTL_MS + delete process.env.AUTH_OIDC_INTERACTION_TTL_SECONDS + delete process.env.AUTH_OIDC_GRANT_TTL_SECONDS + delete process.env.AUTH_OIDC_SESSION_TTL_SECONDS + teardownEmbeddedAuth($envSnapshot) + delete require.cache[require.resolve('../../../src/config/auth-oidc-ttl')] + delete require.cache[require.resolve('../../../src/config/auth-session-store')] + }) + + it('derives interaction and grant TTL from the BFF session store default', () => { + const { resolveOidcProviderTtls } = require('../../../src/config/auth-oidc-ttl') + const ttls = resolveOidcProviderTtls({ + accessTokenTtlSeconds: 1200, + refreshTokenTtlSeconds: 86400 + }) + + expect(ttls).to.deep.equal({ + accessTokenTtlSeconds: 1200, + refreshTokenTtlSeconds: 86400, + idTokenTtlSeconds: 1200, + interactionTtlSeconds: 600, + grantTtlSeconds: 600, + sessionTtlSeconds: 86400 + }) + }) + + it('uses AuthPolicy refresh token TTL for provider session when not overridden', () => { + const { resolveOidcProviderTtls } = require('../../../src/config/auth-oidc-ttl') + const ttls = resolveOidcProviderTtls({ + refreshTokenTtlSeconds: 1209600 + }) + + expect(ttls.sessionTtlSeconds).to.equal(1209600) + }) + + it('defaults id token TTL to AuthPolicy access token TTL', () => { + const { resolveOidcProviderTtls } = require('../../../src/config/auth-oidc-ttl') + const ttls = resolveOidcProviderTtls({ + accessTokenTtlSeconds: 300, + refreshTokenTtlSeconds: 604800 + }) + + expect(ttls.idTokenTtlSeconds).to.equal(300) + }) + + it('honors explicit OIDC TTL env overrides', () => { + process.env.AUTH_SESSION_STORE_TTL_MS = '300000' + process.env.AUTH_OIDC_INTERACTION_TTL_SECONDS = '900' + process.env.AUTH_OIDC_GRANT_TTL_SECONDS = '1200' + process.env.AUTH_OIDC_SESSION_TTL_SECONDS = '3600' + + const { resolveOidcProviderTtls } = require('../../../src/config/auth-oidc-ttl') + const ttls = resolveOidcProviderTtls() + + expect(ttls.interactionTtlSeconds).to.equal(900) + expect(ttls.grantTtlSeconds).to.equal(1200) + expect(ttls.sessionTtlSeconds).to.equal(3600) + }) + + it('loads TTLs from AuthPolicy via the database', async () => { + const db = { + AuthPolicy: { + findByPk: async () => ({ + get: () => ({ + accessTokenTtlSeconds: 1800, + refreshTokenTtlSeconds: 259200 + }) + }) + } + } + + const { loadOidcProviderTtls } = require('../../../src/config/auth-oidc-ttl') + const ttls = await loadOidcProviderTtls(db) + + expect(ttls.accessTokenTtlSeconds).to.equal(1800) + expect(ttls.refreshTokenTtlSeconds).to.equal(259200) + expect(ttls.sessionTtlSeconds).to.equal(259200) + expect(ttls.interactionTtlSeconds).to.equal(600) + }) +}) diff --git a/test/src/config/auth-session-store.test.js b/test/src/config/auth-session-store.test.js new file mode 100644 index 00000000..ce22bcf8 --- /dev/null +++ b/test/src/config/auth-session-store.test.js @@ -0,0 +1,161 @@ +'use strict' + +const { expect } = require('chai') +const sinon = require('sinon') +const { + snapshotOidcEnv, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') + +describe('Auth session store', () => { + def('envSnapshot', () => snapshotOidcEnv()) + + afterEach(() => { + teardownEmbeddedAuth($envSnapshot) + delete process.env.AUTH_SESSION_STORE_TYPE + delete process.env.DB_PROVIDER + delete process.env.AUTH_SESSION_SECRET + delete require.cache[require.resolve('../../../src/config/auth-session-store')] + delete require.cache[require.resolve('../../../src/data/stores/sequelize-session-store')] + }) + + it('defaults to memory store type for sqlite', () => { + const authSessionStore = require('../../../src/config/auth-session-store') + expect(authSessionStore.getSessionStoreConfig()).to.deep.equal({ + type: 'memory', + ttlMs: 10 * 60 * 1000 + }) + }) + + it('defaults to database store type for postgres', () => { + process.env.DB_PROVIDER = 'postgres' + const authSessionStore = require('../../../src/config/auth-session-store') + expect(authSessionStore.getSessionStoreConfig().type).to.equal('database') + }) + + it('allows explicit memory override with external database provider', () => { + process.env.DB_PROVIDER = 'mysql' + process.env.AUTH_SESSION_STORE_TYPE = 'memory' + const authSessionStore = require('../../../src/config/auth-session-store') + expect(authSessionStore.getSessionStoreConfig().type).to.equal('memory') + }) + + it('creates a memory store by default', () => { + const authSessionStore = require('../../../src/config/auth-session-store') + authSessionStore.setSessionSecretForTests('test-session-secret') + const store = authSessionStore.initAuthSessionStore() + expect(store).to.have.property('get') + expect(store).to.have.property('set') + expect(store).to.have.property('destroy') + }) + + it('rejects invalid store types', () => { + process.env.AUTH_SESSION_STORE_TYPE = 'redis' + const authSessionStore = require('../../../src/config/auth-session-store') + expect(() => authSessionStore.getSessionStoreConfig()).to.throw('Invalid auth.sessionStore.type') + }) + + it('round-trips session data through the database store', async () => { + process.env.AUTH_SESSION_STORE_TYPE = 'database' + + const rows = new Map() + const model = { + findByPk: sinon.stub().callsFake(async (sid) => rows.get(sid) || null), + upsert: sinon.stub().callsFake(async (row) => { + rows.set(row.sid, row) + return [row] + }), + destroy: sinon.stub().callsFake(async ({ where }) => { + if (where.sid) { + rows.delete(where.sid) + } + }), + update: sinon.stub().callsFake(async (fields, { where }) => { + const existing = rows.get(where.sid) + if (existing) { + rows.set(where.sid, { ...existing, ...fields }) + } + return [1] + }) + } + + const db = require('../../../src/data/models') + const originalModel = db.AuthBffSession + db.AuthBffSession = model + + const authSessionStore = require('../../../src/config/auth-session-store') + authSessionStore.setSessionSecretForTests('test-session-secret') + authSessionStore.resetAuthSessionStoreForTests() + const store = authSessionStore.initAuthSessionStore() + + const sessionData = { cookie: { maxAge: 600000 }, controllerOauth: { state: 'abc', nonce: 'xyz' } } + await new Promise((resolve, reject) => { + store.set('session-1', sessionData, (error) => (error ? reject(error) : resolve())) + }) + + const loaded = await new Promise((resolve, reject) => { + store.get('session-1', (error, value) => (error ? reject(error) : resolve(value))) + }) + + db.AuthBffSession = originalModel + authSessionStore.resetAuthSessionStoreForTests() + expect(loaded).to.deep.equal(sessionData) + }) + + it('generates and persists session secret when unset', async () => { + const meta = { + id: 1, + sessionSecretRef: null, + update: sinon.stub().resolves() + } + + const db = require('../../../src/data/models') + const originalMeta = db.AuthBootstrapMeta + db.AuthBootstrapMeta = { + findByPk: sinon.stub().resolves(meta), + create: sinon.stub().resolves(meta) + } + + const secretHelper = require('../../../src/helpers/secret-helper') + const encryptStub = sinon.stub(secretHelper, 'encryptSecret').callsFake(async (data) => `enc:${data.secret}`) + const decryptStub = sinon.stub(secretHelper, 'decryptSecret').callsFake(async (ref) => ({ secret: ref.replace('enc:', '') })) + + const authSessionStore = require('../../../src/config/auth-session-store') + authSessionStore.resetAuthSessionStoreForTests() + const secret = await authSessionStore.resolveSessionSecret() + + expect(secret).to.be.a('string').that.is.not.empty + expect(encryptStub).to.have.been.calledOnce + expect(authSessionStore.getSessionSecret()).to.equal(secret) + + encryptStub.restore() + decryptStub.restore() + db.AuthBootstrapMeta = originalMeta + authSessionStore.resetAuthSessionStoreForTests() + }) + + it('reuses persisted session secret on restart', async () => { + const db = require('../../../src/data/models') + const originalMeta = db.AuthBootstrapMeta + db.AuthBootstrapMeta = { + findByPk: sinon.stub().resolves({ + id: 1, + sessionSecretRef: 'enc:persisted-secret' + }), + create: sinon.stub() + } + + const secretHelper = require('../../../src/helpers/secret-helper') + const decryptStub = sinon.stub(secretHelper, 'decryptSecret').resolves({ secret: 'persisted-secret' }) + + const authSessionStore = require('../../../src/config/auth-session-store') + authSessionStore.resetAuthSessionStoreForTests() + const secret = await authSessionStore.resolveSessionSecret() + + expect(secret).to.equal('persisted-secret') + + decryptStub.restore() + db.AuthBootstrapMeta = originalMeta + authSessionStore.resetAuthSessionStoreForTests() + }) +}) diff --git a/test/src/config/auth-token-ttl.test.js b/test/src/config/auth-token-ttl.test.js new file mode 100644 index 00000000..54e00761 --- /dev/null +++ b/test/src/config/auth-token-ttl.test.js @@ -0,0 +1,38 @@ +'use strict' + +const { expect } = require('chai') +const { DEFAULT_POLICY } = require('../../../src/config/auth-policy-defaults') + +describe('auth-token-ttl', () => { + afterEach(() => { + delete process.env.AUTH_ACCESS_TOKEN_TTL_SECONDS + delete process.env.AUTH_REFRESH_TOKEN_TTL_SECONDS + delete require.cache[require.resolve('../../../src/config/auth-token-ttl')] + }) + + it('applies env overrides without mutating AuthPolicy defaults', () => { + process.env.AUTH_ACCESS_TOKEN_TTL_SECONDS = '1200' + process.env.AUTH_REFRESH_TOKEN_TTL_SECONDS = '7200' + + const { applyTokenTtlOverrides } = require('../../../src/config/auth-token-ttl') + const result = applyTokenTtlOverrides({ + accessTokenTtlSeconds: 900, + refreshTokenTtlSeconds: 3600 + }) + + expect(result.accessTokenTtlSeconds).to.equal(1200) + expect(result.refreshTokenTtlSeconds).to.equal(7200) + expect(DEFAULT_POLICY.refreshTokenTtlSeconds).to.equal(3600) + }) + + it('falls back to policy values when overrides are unset', () => { + const { applyTokenTtlOverrides } = require('../../../src/config/auth-token-ttl') + const result = applyTokenTtlOverrides({ + accessTokenTtlSeconds: 900, + refreshTokenTtlSeconds: 3600 + }) + + expect(result.accessTokenTtlSeconds).to.equal(900) + expect(result.refreshTokenTtlSeconds).to.equal(3600) + }) +}) diff --git a/test/src/config/embedded-oidc-client-secret.test.js b/test/src/config/embedded-oidc-client-secret.test.js new file mode 100644 index 00000000..36e7110f --- /dev/null +++ b/test/src/config/embedded-oidc-client-secret.test.js @@ -0,0 +1,146 @@ +'use strict' + +const { expect } = require('chai') +const sinon = require('sinon') +const { + getConfidentialClientId, + resolveConfidentialClientSecret +} = require('../../../src/config/embedded-oidc-client-secret') +const { + snapshotOidcEnv, + restoreOidcEnv, + applyOidcEnv +} = require('../../support/oidc-test-helpers') + +describe('embedded-oidc-client-secret', () => { + def('envSnapshot', () => snapshotOidcEnv()) + def('sandbox', () => sinon.createSandbox()) + + afterEach(() => { + $sandbox.restore() + restoreOidcEnv($envSnapshot) + }) + + it('returns env client secret when configured', async () => { + applyOidcEnv({ + OIDC_CLIENT_ID: 'controller', + OIDC_CLIENT_SECRET: 'env-client-secret' + }) + + const secretHelper = require('../../../src/helpers/secret-helper') + $sandbox.stub(secretHelper, 'encryptSecret').resolves('encrypted-ref') + + const db = { + AuthOidcClient: { + findOne: sinon.stub().resolves(null), + create: sinon.stub().resolves({ clientId: 'controller', secretRef: 'encrypted-ref' }), + update: sinon.stub() + } + } + + const result = await resolveConfidentialClientSecret(db) + + expect(result).to.deep.equal({ + clientId: 'controller', + clientSecret: 'env-client-secret' + }) + expect(db.AuthOidcClient.create).to.have.been.calledOnce + }) + + it('reconciles DB secret when env value changes', async () => { + applyOidcEnv({ + OIDC_CLIENT_ID: 'controller', + OIDC_CLIENT_SECRET: 'new-env-secret' + }) + + const secretHelper = require('../../../src/helpers/secret-helper') + $sandbox.stub(secretHelper, 'decryptSecret').resolves({ secret: 'old-db-secret' }) + $sandbox.stub(secretHelper, 'encryptSecret').resolves('encrypted-new-ref') + + const existingRow = { + clientId: 'controller', + secretRef: 'old-ref', + update: sinon.stub().resolves() + } + + const db = { + AuthOidcClient: { + findOne: sinon.stub().resolves(existingRow), + create: sinon.stub(), + update: sinon.stub().resolves() + } + } + + const result = await resolveConfidentialClientSecret(db) + + expect(result.clientSecret).to.equal('new-env-secret') + expect(existingRow.update).to.have.been.calledOnce + }) + + it('loads client secret from AuthOidcClients when env is unset', async () => { + applyOidcEnv({ OIDC_CLIENT_ID: 'controller' }) + + const db = { + AuthOidcClient: { + findOne: sinon.stub().resolves({ + clientId: 'controller', + secretRef: 'stored-secret-ref' + }), + create: sinon.stub() + } + } + + const secretHelper = require('../../../src/helpers/secret-helper') + $sandbox.stub(secretHelper, 'decryptSecret').resolves({ secret: 'db-client-secret' }) + + const result = await resolveConfidentialClientSecret(db) + + expect(result).to.deep.equal({ + clientId: 'controller', + clientSecret: 'db-client-secret' + }) + }) + + it('generates and persists a secret when createIfMissing is true', async () => { + applyOidcEnv({ OIDC_CLIENT_ID: 'controller' }) + + const createdRow = { clientId: 'controller', secretRef: 'generated-ref' } + const db = { + AuthOidcClient: { + findOne: sinon.stub().resolves(null), + create: sinon.stub().resolves(createdRow) + } + } + + const secretHelper = require('../../../src/helpers/secret-helper') + $sandbox.stub(secretHelper, 'encryptSecret').callsFake(async (data) => data.secret) + + const result = await resolveConfidentialClientSecret(db, { createIfMissing: true }) + + expect(result.clientId).to.equal('controller') + expect(result.clientSecret).to.be.a('string').that.is.not.empty + expect(db.AuthOidcClient.create).to.have.been.calledOnce + }) + + it('throws when secret is unavailable and createIfMissing is false', async () => { + applyOidcEnv({ OIDC_CLIENT_ID: 'controller' }) + + const db = { + AuthOidcClient: { + findOne: sinon.stub().resolves(null) + } + } + + try { + await resolveConfidentialClientSecret(db) + expect.fail('expected secret resolution to fail') + } catch (error) { + expect(error.message).to.include('Embedded OIDC client secret') + } + }) + + it('defaults client id to controller', () => { + applyOidcEnv({}) + expect(getConfidentialClientId()).to.equal('controller') + }) +}) From 5a3039bc88fde5ba1253a731c2f8174cad958dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 17:04:32 +0300 Subject: [PATCH 51/75] Harden bootstrap reconciliation, tokens, and forced password enforcement. Reconcile bootstrap admin on startup, issue password-change claims for CLI, gate RBAC when change is required, and expand auth regression coverage. --- src/lib/rbac/middleware.js | 130 ++++++- src/services/auth-bootstrap-service.js | 164 +++++--- src/services/auth-login-service.js | 3 +- src/services/auth-token-service.js | 128 +++++-- src/services/user-service.js | 52 ++- .../services/auth-bootstrap-service.test.js | 353 ++++++++++++++++++ .../src/services/auth-forced-password.test.js | 173 +++++++++ test/src/services/auth-integration.test.js | 13 +- test/src/services/auth-token-service.test.js | 78 ++++ 9 files changed, 999 insertions(+), 95 deletions(-) create mode 100644 test/src/services/auth-bootstrap-service.test.js create mode 100644 test/src/services/auth-forced-password.test.js create mode 100644 test/src/services/auth-token-service.test.js diff --git a/src/lib/rbac/middleware.js b/src/lib/rbac/middleware.js index 1c6abbb1..2410050a 100644 --- a/src/lib/rbac/middleware.js +++ b/src/lib/rbac/middleware.js @@ -17,7 +17,14 @@ const yaml = require('js-yaml') const authorizer = require('./authorizer') const logger = require('../../logger') const config = require('../../config') +const db = require('../../data/models') const { getOidcSettings } = require('../../config/oidc') +const { PASSWORD_CHANGE_REQUIRED_CLAIM } = require('../../services/auth-token-service') + +const PASSWORD_CHANGE_ALLOWLIST = [ + { method: 'GET', path: '/api/v3/user/profile' }, + { method: 'POST', path: '/api/v3/user/change-password' } +] // Load route resource catalog let routeCatalog = null @@ -91,6 +98,84 @@ function extractGroupSubjects (tokenContent) { return groups } +function normalizeRequestPath (path) { + let normalizedPath = path + try { + if (path.includes('?')) { + normalizedPath = path.split('?')[0] + } + if (normalizedPath.includes('#')) { + normalizedPath = normalizedPath.split('#')[0] + } + } catch (error) { + // If parsing fails, use path as-is + } + return normalizedPath.replace(/\/$/, '') +} + +function tokenRequiresPasswordChange (tokenContent) { + return Boolean(tokenContent && tokenContent[PASSWORD_CHANGE_REQUIRED_CLAIM] === true) +} + +function isPasswordChangeAllowlistedRoute (method, path) { + const normalizedPath = normalizeRequestPath(path) + return PASSWORD_CHANGE_ALLOWLIST.some((route) => ( + route.method === method.toUpperCase() && route.path === normalizedPath + )) +} + +function denyPasswordChangeRequired (res) { + return res.status(403).json({ + error: 'Forbidden', + message: 'Password change required before accessing this resource' + }) +} + +async function userStillRequiresPasswordChange (tokenContent) { + if (!tokenRequiresPasswordChange(tokenContent)) { + return false + } + + const userId = tokenContent.sub + if (!userId) { + return true + } + + const user = await db.AuthUser.findByPk(userId, { + attributes: ['mustChangePassword', 'deletedAt'] + }) + + if (!user || user.deletedAt) { + return true + } + + return Boolean(user.mustChangePassword) +} + +async function enforcePasswordChangeGate (req, res, tokenContent) { + if (!await userStillRequiresPasswordChange(tokenContent)) { + return false + } + if (isPasswordChangeAllowlistedRoute(req.method, req.path)) { + return false + } + denyPasswordChangeRequired(res) + return true +} + +async function enforcePasswordChangeGateForWebSocket (tokenContent, path) { + if (!await userStillRequiresPasswordChange(tokenContent)) { + return { blocked: false } + } + if (isPasswordChangeAllowlistedRoute('WS', path)) { + return { blocked: false } + } + return { + blocked: true, + reason: 'Password change required before accessing this resource' + } +} + /** * Extract subjects from request (OIDC bearer token via req.kauth or ServiceAccount JWT) */ @@ -132,19 +217,7 @@ function findRouteDefinition (method, path) { // Normalize path (remove trailing slashes and query parameters) // Extract pathname only (remove query string and hash) - let normalizedPath = path - try { - // If path contains query parameters, extract just the pathname - if (path.includes('?')) { - normalizedPath = path.split('?')[0] - } - if (normalizedPath.includes('#')) { - normalizedPath = normalizedPath.split('#')[0] - } - } catch (error) { - // If parsing fails, use path as-is - } - normalizedPath = normalizedPath.replace(/\/$/, '') + const normalizedPath = normalizeRequestPath(path) for (const [resourceName, resourceDef] of Object.entries(catalog.resources)) { if (!resourceDef.routes || !Array.isArray(resourceDef.routes)) { @@ -240,6 +313,11 @@ function requirePermission (resource, verb) { return res.status(401).json({ error: 'Unauthorized: No authentication information found' }) } + const tokenContent = req.kauth.grant.access_token.content + if (await enforcePasswordChangeGate(req, res, tokenContent)) { + return + } + // Find route definition const routeDef = findRouteDefinition(req.method, req.path) if (!routeDef) { @@ -312,6 +390,23 @@ async function authorizeWebSocket (req, token) { return { allowed: false, reason: 'No subjects found in WebSocket token' } } + let tokenPayload = null + if (token) { + try { + const tokenParts = token.replace('Bearer ', '').split('.') + if (tokenParts.length === 3) { + tokenPayload = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString()) + } + } catch (error) { + logger.warn('Failed to parse WebSocket token for password-change gate:', error) + } + } + + const passwordChangeGate = await enforcePasswordChangeGateForWebSocket(tokenPayload, req.url) + if (passwordChangeGate.blocked) { + return { allowed: false, reason: passwordChangeGate.reason } + } + // Find route definition (use 'WS' as method) const routeDef = findRouteDefinition('WS', req.url) if (!routeDef) { @@ -477,6 +572,11 @@ function protect (_roles) { return res.status(401).json({ error: 'Unauthorized: No authentication information found' }) } + const tokenContent = req.kauth.grant.access_token.content + if (await enforcePasswordChangeGate(req, res, tokenContent)) { + return + } + // Find route definition const routeDef = findRouteDefinition(req.method, req.path) if (!routeDef) { @@ -537,7 +637,9 @@ module.exports = { skipRBAC, extractSubjects, extractSubjectsFromWebSocket, - findRouteDefinition + findRouteDefinition, + tokenRequiresPasswordChange, + isPasswordChangeAllowlistedRoute } diff --git a/src/services/auth-bootstrap-service.js b/src/services/auth-bootstrap-service.js index 6c5bb754..f4e078fe 100644 --- a/src/services/auth-bootstrap-service.js +++ b/src/services/auth-bootstrap-service.js @@ -7,10 +7,15 @@ const db = require('../data/models') const secretHelper = require('../helpers/secret-helper') const AuthPasswordService = require('./auth-password-service') const AuthPolicyService = require('./auth-policy-service') +const AuthTokenService = require('./auth-token-service') const { ADMIN_GROUP } = require('./auth-mfa-service') const SYSTEM_GROUPS = ['admin', 'sre', 'developer', 'viewer'] +function normalizeBootstrapUsername (username) { + return String(username || '').trim().toLowerCase() +} + async function resolveBootstrapPassword (passwordRef) { if (!passwordRef) { return null @@ -31,7 +36,7 @@ async function resolveBootstrapPassword (passwordRef) { function getBootstrapConfig () { return { - email: (process.env.OIDC_BOOTSTRAP_ADMIN_EMAIL || config.get('auth.bootstrap.adminEmail') || '').trim().toLowerCase(), + username: (process.env.OIDC_BOOTSTRAP_ADMIN_USERNAME || config.get('auth.bootstrap.adminUsername') || '').trim(), passwordRef: process.env.OIDC_BOOTSTRAP_ADMIN_PASSWORD || config.get('auth.bootstrap.adminPassword') || '', allowBootstrapLog: config.get('auth.insecureAllowBootstrapLog', false) === true } @@ -47,6 +52,61 @@ async function ensureSystemGroups (transaction) { } } +async function findBootstrapUser (transaction) { + return db.AuthUser.findOne({ + where: { isBootstrap: true }, + transaction + }) +} + +async function hardDeleteBootstrapUser (user, transaction) { + await AuthTokenService.revokeAllUserRefreshTokens(user.id, transaction) + await db.AuthUserGroup.destroy({ where: { userId: user.id }, transaction }) + await db.AuthMfa.destroy({ where: { userId: user.id }, transaction }) + await db.AuthPasswordResetSession.destroy({ where: { userId: user.id }, transaction }) + await db.AuthRefreshToken.destroy({ where: { userId: user.id }, transaction }) + await user.destroy({ transaction }) +} + +async function bootstrapMatchesEnv (bootstrapUser, normalizedUsername, plainPassword) { + if (bootstrapUser.email !== normalizedUsername) { + return false + } + return AuthPasswordService.verifyPassword(plainPassword, bootstrapUser.passwordHash) +} + +async function createBootstrapUser (normalizedUsername, plainPassword, transaction, allowBootstrapLog) { + const adminGroup = await db.AuthGroup.findOne({ + where: { name: ADMIN_GROUP }, + transaction + }) + if (!adminGroup) { + throw new Error('System admin group is missing from AuthGroups') + } + + const userId = crypto.randomUUID() + const passwordHash = await AuthPasswordService.hashPassword(plainPassword) + const user = await db.AuthUser.create({ + id: userId, + email: normalizedUsername, + passwordHash, + isBootstrap: true, + mustChangePassword: false + }, { transaction }) + + await db.AuthUserGroup.create({ + userId: user.id, + groupId: adminGroup.id + }, { transaction }) + + logger.info(`Embedded auth bootstrap admin created for ${normalizedUsername}`) + if (allowBootstrapLog) { + logger.warn(`Bootstrap admin temporary password for ${normalizedUsername}: ${plainPassword}`) + } + + return user +} + async function runBootstrap (outerTransaction) { const transaction = outerTransaction || await db.sequelize.transaction() const ownTransaction = !outerTransaction @@ -62,87 +122,81 @@ async function runBootstrap (outerTransaction) { meta = await db.AuthBootstrapMeta.create({ id: 1 }, { transaction }) } - if (meta.completedAt) { - if (ownTransaction) { - await transaction.commit() - } - return { skipped: true, reason: 'already_completed' } - } + const existingBootstrap = await findBootstrapUser(transaction) + const { username, passwordRef, allowBootstrapLog } = getBootstrapConfig() - const { email, passwordRef, allowBootstrapLog } = getBootstrapConfig() - if (!email || !passwordRef) { - logger.warn('Embedded auth bootstrap skipped: OIDC_BOOTSTRAP_ADMIN_EMAIL and OIDC_BOOTSTRAP_ADMIN_PASSWORD are required for first boot') + if (!username || !passwordRef) { + if (existingBootstrap) { + logger.warn('Embedded auth bootstrap env missing; keeping existing bootstrap admin') + } else { + logger.warn('Embedded auth bootstrap skipped: OIDC_BOOTSTRAP_ADMIN_USERNAME and OIDC_BOOTSTRAP_ADMIN_PASSWORD are required for first boot') + } if (ownTransaction) { await transaction.commit() } - return { skipped: true, reason: 'missing_credentials' } + return { skipped: true, reason: existingBootstrap ? 'env_missing_keep_existing' : 'missing_credentials' } } const plainPassword = await resolveBootstrapPassword(passwordRef) if (!plainPassword) { - logger.warn('Embedded auth bootstrap skipped: bootstrap admin password could not be resolved') + if (existingBootstrap) { + logger.warn('Embedded auth bootstrap password could not be resolved; keeping existing bootstrap admin') + } else { + logger.warn('Embedded auth bootstrap skipped: bootstrap admin password could not be resolved') + } if (ownTransaction) { await transaction.commit() } - return { skipped: true, reason: 'missing_credentials' } + return { skipped: true, reason: existingBootstrap ? 'env_missing_keep_existing' : 'missing_credentials' } } const policy = await AuthPolicyService.getPolicy(transaction) AuthPasswordService.validatePasswordComplexity(plainPassword, policy) - - const existingUser = await db.AuthUser.findOne({ - where: { email, deletedAt: null }, - transaction - }) - if (existingUser) { - await meta.update({ - completedAt: new Date(), - bootstrapAdminUserId: existingUser.id - }, { transaction }) - logger.info(`Embedded auth bootstrap marked complete for existing user ${email}`) - if (ownTransaction) { - await transaction.commit() + const normalizedUsername = normalizeBootstrapUsername(username) + + if (existingBootstrap) { + if (await bootstrapMatchesEnv(existingBootstrap, normalizedUsername, plainPassword)) { + await meta.update({ + completedAt: new Date(), + bootstrapAdminUserId: existingBootstrap.id + }, { transaction }) + if (ownTransaction) { + await transaction.commit() + } + return { skipped: true, reason: 'unchanged', userId: existingBootstrap.id, username: normalizedUsername } } - return { skipped: true, reason: 'user_exists', userId: existingUser.id } - } - const adminGroup = await db.AuthGroup.findOne({ - where: { name: ADMIN_GROUP }, - transaction - }) - if (!adminGroup) { - throw new Error('System admin group is missing from AuthGroups') + logger.info(`Embedded auth bootstrap admin rotation: replacing ${existingBootstrap.email}`) + await hardDeleteBootstrapUser(existingBootstrap, transaction) + } else { + const conflictingUser = await db.AuthUser.findOne({ + where: { email: normalizedUsername, deletedAt: null }, + transaction + }) + if (conflictingUser) { + logger.warn(`Embedded auth bootstrap skipped: user ${normalizedUsername} already exists and is not bootstrap`) + await meta.update({ + completedAt: new Date(), + bootstrapAdminUserId: conflictingUser.id + }, { transaction }) + if (ownTransaction) { + await transaction.commit() + } + return { skipped: true, reason: 'user_exists', userId: conflictingUser.id } + } } - const userId = crypto.randomUUID() - const passwordHash = await AuthPasswordService.hashPassword(plainPassword) - const user = await db.AuthUser.create({ - id: userId, - email, - passwordHash, - isBootstrap: true, - mustChangePassword: false - }, { transaction }) - - await db.AuthUserGroup.create({ - userId: user.id, - groupId: adminGroup.id - }, { transaction }) + const user = await createBootstrapUser(normalizedUsername, plainPassword, transaction, allowBootstrapLog) await meta.update({ completedAt: new Date(), bootstrapAdminUserId: user.id }, { transaction }) - logger.info(`Embedded auth bootstrap admin created for ${email}`) - if (allowBootstrapLog) { - logger.warn(`Bootstrap admin temporary password for ${email}: ${plainPassword}`) - } - if (ownTransaction) { await transaction.commit() } - return { skipped: false, userId: user.id, email } + return { skipped: false, userId: user.id, username: normalizedUsername } } catch (error) { if (ownTransaction) { await transaction.rollback() @@ -154,5 +208,7 @@ async function runBootstrap (outerTransaction) { module.exports = { SYSTEM_GROUPS, ensureSystemGroups, + getBootstrapConfig, + normalizeBootstrapUsername, runBootstrap } diff --git a/src/services/auth-login-service.js b/src/services/auth-login-service.js index 5777ed43..f3b53e17 100644 --- a/src/services/auth-login-service.js +++ b/src/services/auth-login-service.js @@ -58,7 +58,8 @@ async function profile (req, transaction) { sub: claims.sub, email: claims.email, preferred_username: claims.preferred_username, - groups: claims.groups || [] + groups: claims.groups || [], + password_change_required: claims.password_change_required === true } } diff --git a/src/services/auth-token-service.js b/src/services/auth-token-service.js index 1e96b3a9..69532958 100644 --- a/src/services/auth-token-service.js +++ b/src/services/auth-token-service.js @@ -1,7 +1,7 @@ 'use strict' const crypto = require('crypto') -const { SignJWT } = require('jose') +const { SignJWT, jwtVerify } = require('jose') const db = require('../data/models') const { getOidcSettings } = require('../config/oidc') const AuthJwks = require('../config/auth-jwks') @@ -9,12 +9,32 @@ const { getPolicy } = require('./auth-policy-service') const { withTransaction } = require('../helpers/app-helper') const Errors = require('../helpers/errors') -function hashRefreshToken (token) { - return crypto.createHash('sha256').update(token).digest('hex') +const PASSWORD_CHANGE_REQUIRED_CLAIM = 'password_change_required' +const ACCESS_TOKEN_USE = 'access' +const REFRESH_TOKEN_USE = 'refresh' + +function buildUserAccessClaims (user, groupNames) { + const identifier = user.email + const claims = { + preferred_username: identifier, + groups: groupNames, + token_use: ACCESS_TOKEN_USE + } + if (String(identifier).includes('@')) { + claims.email = identifier + } + if (user.mustChangePassword) { + claims[PASSWORD_CHANGE_REQUIRED_CLAIM] = true + } + return claims +} + +function hashTokenJti (jti) { + return crypto.createHash('sha256').update(jti).digest('hex') } -function createOpaqueRefreshToken () { - return crypto.randomBytes(32).toString('base64url') +function hashRefreshToken (value) { + return hashTokenJti(value) } async function issueAccessToken (user, groupNames, policy, transaction) { @@ -22,11 +42,9 @@ async function issueAccessToken (user, groupNames, policy, transaction) { const { kid, signingKey } = await AuthJwks.getActiveSigningMaterial(transaction) const ttlSeconds = policy.accessTokenTtlSeconds || 900 - return new SignJWT({ - preferred_username: user.email, - email: user.email, - groups: groupNames - }) + const claims = buildUserAccessClaims(user, groupNames) + + return new SignJWT(claims) .setProtectedHeader({ alg: 'RS256', kid }) .setSubject(user.id) .setIssuer(issuerUrl) @@ -36,10 +54,10 @@ async function issueAccessToken (user, groupNames, policy, transaction) { .sign(signingKey) } -async function persistRefreshToken (userId, refreshToken, familyId, policy, transaction) { - const ttlSeconds = policy.refreshTokenTtlSeconds || 604800 +async function persistRefreshToken (userId, jti, familyId, policy, transaction) { + const ttlSeconds = policy.refreshTokenTtlSeconds || 3600 await db.AuthRefreshToken.create({ - tokenHash: hashRefreshToken(refreshToken), + tokenHash: hashTokenJti(jti), userId, familyId, expiresAt: new Date(Date.now() + ttlSeconds * 1000), @@ -47,19 +65,63 @@ async function persistRefreshToken (userId, refreshToken, familyId, policy, tran }, withTransaction(transaction)) } +async function issueRefreshToken (user, familyId, policy, transaction) { + const { issuerUrl, clientId } = getOidcSettings() + const { kid, signingKey } = await AuthJwks.getActiveSigningMaterial(transaction) + const jti = crypto.randomUUID() + const ttlSeconds = policy.refreshTokenTtlSeconds || 3600 + + const refreshToken = await new SignJWT({ + token_use: REFRESH_TOKEN_USE, + family_id: familyId + }) + .setProtectedHeader({ alg: 'RS256', kid }) + .setSubject(user.id) + .setIssuer(issuerUrl) + .setAudience(clientId) + .setJti(jti) + .setIssuedAt() + .setExpirationTime(`${ttlSeconds}s`) + .sign(signingKey) + + await persistRefreshToken(user.id, jti, familyId, policy, transaction) + return refreshToken +} + async function issueTokenPair (user, groupNames, transaction) { const policy = await getPolicy(transaction) - const accessToken = await issueAccessToken(user, groupNames, policy, transaction) - const refreshToken = createOpaqueRefreshToken() const familyId = crypto.randomUUID() - - await persistRefreshToken(user.id, refreshToken, familyId, policy, transaction) + const accessToken = await issueAccessToken(user, groupNames, policy, transaction) + const refreshToken = await issueRefreshToken(user, familyId, policy, transaction) return { accessToken, refreshToken } } -async function findValidRefreshToken (refreshToken, transaction) { - const tokenHash = hashRefreshToken(refreshToken) +async function verifyRefreshJwt (refreshToken, transaction) { + const { issuerUrl, clientId } = getOidcSettings() + const { signingKey } = await AuthJwks.getActiveSigningMaterial(transaction) + const verifyOptions = { issuer: issuerUrl } + if (clientId) { + verifyOptions.audience = clientId + } + + let payload + try { + const result = await jwtVerify(refreshToken, signingKey, verifyOptions) + payload = result.payload + } catch (error) { + throw new Errors.InvalidCredentialsError() + } + + if (payload.token_use !== REFRESH_TOKEN_USE || !payload.jti) { + throw new Errors.InvalidCredentialsError() + } + + return payload +} + +async function findValidRefreshTokenByJti (jti, transaction) { + const tokenHash = hashTokenJti(jti) const row = await db.AuthRefreshToken.findOne(withTransaction(transaction, { where: { tokenHash, @@ -81,11 +143,20 @@ async function revokeRefreshTokenFamily (familyId, transaction) { } async function rotateRefreshToken (refreshToken, transaction) { - const row = await findValidRefreshToken(refreshToken, transaction) + const claims = await verifyRefreshJwt(refreshToken, transaction) + const row = await findValidRefreshTokenByJti(claims.jti, transaction) if (!row) { throw new Errors.InvalidCredentialsError() } + if (row.userId !== claims.sub) { + throw new Errors.InvalidCredentialsError() + } + + if (claims.family_id && row.familyId !== claims.family_id) { + throw new Errors.InvalidCredentialsError() + } + const policy = await getPolicy(transaction) const user = await db.AuthUser.findByPk(row.userId, withTransaction(transaction, { include: [{ @@ -108,8 +179,7 @@ async function rotateRefreshToken (refreshToken, transaction) { let nextRefreshToken = refreshToken if (policy.refreshRotation) { - nextRefreshToken = createOpaqueRefreshToken() - await persistRefreshToken(user.id, nextRefreshToken, row.familyId, policy, transaction) + nextRefreshToken = await issueRefreshToken(user, row.familyId, policy, transaction) } return { @@ -119,7 +189,14 @@ async function rotateRefreshToken (refreshToken, transaction) { } async function revokeRefreshToken (refreshToken, transaction) { - const row = await findValidRefreshToken(refreshToken, transaction) + let claims + try { + claims = await verifyRefreshJwt(refreshToken, transaction) + } catch (error) { + return + } + + const row = await findValidRefreshTokenByJti(claims.jti, transaction) if (!row) { return } @@ -133,8 +210,13 @@ async function revokeAllUserRefreshTokens (userId, transaction) { } module.exports = { + PASSWORD_CHANGE_REQUIRED_CLAIM, + ACCESS_TOKEN_USE, + REFRESH_TOKEN_USE, + buildUserAccessClaims, issueAccessToken, issueTokenPair, + verifyRefreshJwt, rotateRefreshToken, revokeRefreshToken, revokeAllUserRefreshTokens, diff --git a/src/services/user-service.js b/src/services/user-service.js index 90441244..8d3ccace 100644 --- a/src/services/user-service.js +++ b/src/services/user-service.js @@ -25,6 +25,7 @@ const AuthLoginService = require('./auth-login-service') const AuthMfaService = require('./auth-mfa-service') const AuthUserService = require('./auth-user-service') const AuthOauthService = require('./auth-oauth-service') +const AuthInteractionService = require('./auth-interaction-service') function mapOidcError (error) { const description = error.error_description || error.message || 'Invalid credentials' @@ -194,6 +195,48 @@ const oauthCallback = async function (req, isCLI, transaction) { return AuthOauthService.callback(req) } +const interactionStatus = async function (uid, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.getStatus(uid, transaction) +} + +const interactionLogin = async function (uid, credentials, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.submitLogin(uid, credentials, transaction) +} + +const interactionMfa = async function (uid, code, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.submitMfa(uid, code, transaction) +} + +const interactionEnroll = async function (uid, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.submitEnroll(uid, transaction) +} + +const interactionConfirmEnroll = async function (uid, code, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.submitConfirmEnroll(uid, code, transaction) +} + +const interactionChangePassword = async function (uid, payload, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.submitChangePassword(uid, payload, transaction) +} + +const interactionComplete = async function (uid, req, res, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.complete(uid, req, res, transaction) +} + module.exports = { login: TransactionDecorator.generateTransaction(login), refresh: TransactionDecorator.generateTransaction(refresh), @@ -204,5 +247,12 @@ module.exports = { disableMfa: TransactionDecorator.generateTransaction(disableMfa), changePassword: TransactionDecorator.generateTransaction(changePassword), oauthAuthorize: TransactionDecorator.generateTransaction(oauthAuthorize), - oauthCallback: TransactionDecorator.generateTransaction(oauthCallback) + oauthCallback: TransactionDecorator.generateTransaction(oauthCallback), + interactionStatus: TransactionDecorator.generateTransaction(interactionStatus), + interactionLogin: TransactionDecorator.generateTransaction(interactionLogin), + interactionMfa: TransactionDecorator.generateTransaction(interactionMfa), + interactionEnroll: TransactionDecorator.generateTransaction(interactionEnroll), + interactionConfirmEnroll: TransactionDecorator.generateTransaction(interactionConfirmEnroll), + interactionChangePassword: TransactionDecorator.generateTransaction(interactionChangePassword), + interactionComplete: TransactionDecorator.generateTransaction(interactionComplete) } diff --git a/test/src/services/auth-bootstrap-service.test.js b/test/src/services/auth-bootstrap-service.test.js new file mode 100644 index 00000000..2917f5b7 --- /dev/null +++ b/test/src/services/auth-bootstrap-service.test.js @@ -0,0 +1,353 @@ +'use strict' + +const { expect } = require('chai') +const sinon = require('sinon') +const { decodeJwt } = require('jose') +const AuthPasswordService = require('../../../src/services/auth-password-service') +const { + snapshotOidcEnv, + DEFAULT_TEST_PASSWORD, + createEmbeddedAuthHarness, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') + +const BOOTSTRAP_PASSWORD = 'ChangeMeSecure123!' + +function createRecord (data) { + const record = { + ...data, + dataValues: { ...data } + } + + record.update = async function (fields) { + Object.assign(this, fields) + Object.assign(this.dataValues, fields) + return this + } + + record.destroy = async function () { + record._deleted = true + return undefined + } + + record.get = function ({ plain } = {}) { + if (plain) { + const copy = { ...this } + delete copy.dataValues + delete copy.update + delete copy.destroy + delete copy.get + return copy + } + return this + } + + return record +} + +function createNoopTransaction () { + return { + commit: async () => {}, + rollback: async () => {}, + LOCK: { UPDATE: 'UPDATE' } + } +} + +const DB_MODEL_KEYS = [ + 'sequelize', + 'AuthGroup', + 'AuthBootstrapMeta', + 'AuthUser', + 'AuthUserGroup', + 'AuthMfa', + 'AuthPasswordResetSession', + 'AuthRefreshToken', + 'AuthPolicy' +] + +function snapshotDbModels () { + const db = require('../../../src/data/models') + return Object.fromEntries(DB_MODEL_KEYS.map((key) => [key, db[key]])) +} + +function restoreDbModels (snapshot) { + if (!snapshot) { + return + } + const db = require('../../../src/data/models') + for (const key of DB_MODEL_KEYS) { + db[key] = snapshot[key] + } +} + +function installBootstrapDb (sandbox, state) { + const db = require('../../../src/data/models') + + db.sequelize = { + transaction: sandbox.stub().callsFake(async () => createNoopTransaction()) + } + + db.AuthGroup = { + findOrCreate: sandbox.stub().callsFake(async ({ where, defaults }) => { + const existing = state.groups.get(where.name) + if (existing) { + return [existing, false] + } + const created = createRecord({ id: `grp-${where.name}`, ...defaults }) + state.groups.set(where.name, created) + return [created, true] + }), + findOne: sandbox.stub().callsFake(async ({ where }) => state.groups.get(where.name) || null) + } + + db.AuthBootstrapMeta = { + findByPk: sandbox.stub().callsFake(async () => state.meta), + create: sandbox.stub().callsFake(async (values) => { + state.meta = createRecord(values) + return state.meta + }) + } + + db.AuthUser = { + findOne: sandbox.stub().callsFake(async ({ where }) => { + if (where.isBootstrap) { + return state.bootstrapUser && !state.bootstrapUser._deleted ? state.bootstrapUser : null + } + if (where.email) { + const user = [...state.users.values()].find((row) => row.email === where.email && !row._deleted) + if (!user) { + return null + } + if (where.deletedAt === null && user.deletedAt) { + return null + } + return user + } + return null + }), + create: sandbox.stub().callsFake(async (values) => { + const user = createRecord({ + ...values, + deletedAt: null + }) + state.users.set(user.id, user) + if (values.isBootstrap) { + state.bootstrapUser = user + } + return user + }) + } + + db.AuthUserGroup = { + create: sandbox.stub().resolves({}), + destroy: sandbox.stub().resolves(1) + } + + db.AuthMfa = { + destroy: sandbox.stub().resolves(1) + } + + db.AuthPasswordResetSession = { + destroy: sandbox.stub().resolves(1) + } + + db.AuthRefreshToken = { + destroy: sandbox.stub().resolves(1), + update: sandbox.stub().resolves([1]) + } + state.authRefreshTokenUpdate = db.AuthRefreshToken.update + + db.AuthPolicy = { + findByPk: sandbox.stub().callsFake(async () => state.policy) + } +} + +function reloadBootstrapService () { + delete require.cache[require.resolve('../../../src/services/auth-bootstrap-service')] + return require('../../../src/services/auth-bootstrap-service') +} + +describe('auth-bootstrap-service', () => { + def('sandbox', () => sinon.createSandbox()) + def('dbSnapshot', () => snapshotDbModels()) + + afterEach(() => { + $sandbox.restore() + restoreDbModels($dbSnapshot) + delete process.env.OIDC_BOOTSTRAP_ADMIN_USERNAME + delete process.env.OIDC_BOOTSTRAP_ADMIN_PASSWORD + delete require.cache[require.resolve('../../../src/services/auth-bootstrap-service')] + }) + + it('creates bootstrap admin with non-email username on first boot', async () => { + const state = { + groups: new Map([ + ['admin', createRecord({ id: 'grp-admin', name: 'admin', isSystem: true })] + ]), + users: new Map(), + bootstrapUser: null, + meta: null, + policy: createRecord({ id: 1, ...require('../../../src/services/auth-policy-service').DEFAULT_POLICY }) + } + + installBootstrapDb($sandbox, state) + process.env.OIDC_BOOTSTRAP_ADMIN_USERNAME = 'admin' + process.env.OIDC_BOOTSTRAP_ADMIN_PASSWORD = BOOTSTRAP_PASSWORD + + const { runBootstrap } = reloadBootstrapService() + const result = await runBootstrap() + + expect(result).to.deep.include({ skipped: false, username: 'admin' }) + expect(state.bootstrapUser.email).to.equal('admin') + expect(state.bootstrapUser.isBootstrap).to.equal(true) + expect(await AuthPasswordService.verifyPassword(BOOTSTRAP_PASSWORD, state.bootstrapUser.passwordHash)).to.equal(true) + expect(state.meta.bootstrapAdminUserId).to.equal(state.bootstrapUser.id) + }) + + it('keeps existing bootstrap when env credentials are missing', async () => { + const existing = createRecord({ + id: 'bootstrap-1', + email: 'admin', + passwordHash: await AuthPasswordService.hashPassword(BOOTSTRAP_PASSWORD), + isBootstrap: true + }) + + const state = { + groups: new Map([ + ['admin', createRecord({ id: 'grp-admin', name: 'admin', isSystem: true })] + ]), + users: new Map([[existing.id, existing]]), + bootstrapUser: existing, + meta: createRecord({ id: 1, completedAt: new Date(), bootstrapAdminUserId: existing.id }), + policy: createRecord({ id: 1, ...require('../../../src/services/auth-policy-service').DEFAULT_POLICY }) + } + + installBootstrapDb($sandbox, state) + + const { runBootstrap } = reloadBootstrapService() + const result = await runBootstrap() + + expect(result).to.deep.equal({ skipped: true, reason: 'env_missing_keep_existing' }) + expect(existing._deleted).to.not.equal(true) + }) + + it('skips rotation when env matches existing bootstrap credentials', async () => { + const existing = createRecord({ + id: 'bootstrap-1', + email: 'admin', + passwordHash: await AuthPasswordService.hashPassword(BOOTSTRAP_PASSWORD), + isBootstrap: true + }) + + const state = { + groups: new Map([ + ['admin', createRecord({ id: 'grp-admin', name: 'admin', isSystem: true })] + ]), + users: new Map([[existing.id, existing]]), + bootstrapUser: existing, + meta: createRecord({ id: 1 }), + policy: createRecord({ id: 1, ...require('../../../src/services/auth-policy-service').DEFAULT_POLICY }) + } + + installBootstrapDb($sandbox, state) + process.env.OIDC_BOOTSTRAP_ADMIN_USERNAME = 'admin' + process.env.OIDC_BOOTSTRAP_ADMIN_PASSWORD = BOOTSTRAP_PASSWORD + + const { runBootstrap } = reloadBootstrapService() + const result = await runBootstrap() + + expect(result).to.deep.include({ skipped: true, reason: 'unchanged', userId: existing.id, username: 'admin' }) + expect(state.authRefreshTokenUpdate.called).to.equal(false) + expect(existing._deleted).to.not.equal(true) + }) + + it('rotates bootstrap when env password changes', async () => { + const existing = createRecord({ + id: 'bootstrap-1', + email: 'admin', + passwordHash: await AuthPasswordService.hashPassword('OldPassword123!'), + isBootstrap: true + }) + + const state = { + groups: new Map([ + ['admin', createRecord({ id: 'grp-admin', name: 'admin', isSystem: true })] + ]), + users: new Map([[existing.id, existing]]), + bootstrapUser: existing, + meta: createRecord({ id: 1, completedAt: new Date(), bootstrapAdminUserId: existing.id }), + policy: createRecord({ id: 1, ...require('../../../src/services/auth-policy-service').DEFAULT_POLICY }) + } + + installBootstrapDb($sandbox, state) + process.env.OIDC_BOOTSTRAP_ADMIN_USERNAME = 'admin' + process.env.OIDC_BOOTSTRAP_ADMIN_PASSWORD = BOOTSTRAP_PASSWORD + + const { runBootstrap } = reloadBootstrapService() + const result = await runBootstrap() + + expect(result).to.deep.include({ skipped: false, username: 'admin' }) + expect(state.authRefreshTokenUpdate.calledOnce).to.equal(true) + expect(state.authRefreshTokenUpdate.firstCall.args[0]).to.deep.equal({ revoked: true }) + expect(state.authRefreshTokenUpdate.firstCall.args[1].where).to.deep.equal({ + userId: existing.id, + revoked: false + }) + expect(existing._deleted).to.equal(true) + expect(state.bootstrapUser.id).to.not.equal(existing.id) + expect(state.bootstrapUser.email).to.equal('admin') + expect(await AuthPasswordService.verifyPassword(BOOTSTRAP_PASSWORD, state.bootstrapUser.passwordHash)).to.equal(true) + }) +}) + +describe('Bootstrap username login + JWT claims', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + + beforeEach(async () => { + await $harness + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + it('allows login with non-email username and omits email JWT claim', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'admin', + groupNames: ['admin'], + isBootstrap: true + }) + + const result = await modules.UserService.login({ + email: 'admin', + password: DEFAULT_TEST_PASSWORD + }, false) + + const claims = decodeJwt(result.accessToken) + expect(claims.preferred_username).to.equal('admin') + expect(claims).to.not.have.property('email') + expect(result.accessToken).to.be.a('string').that.is.not.empty + }) + + it('includes email JWT claim when identifier contains @', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'bootstrap@example.com', + groupNames: ['admin'], + isBootstrap: true + }) + + const result = await modules.UserService.login({ + email: 'bootstrap@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const claims = decodeJwt(result.accessToken) + expect(claims.preferred_username).to.equal('bootstrap@example.com') + expect(claims.email).to.equal('bootstrap@example.com') + }) +}) diff --git a/test/src/services/auth-forced-password.test.js b/test/src/services/auth-forced-password.test.js new file mode 100644 index 00000000..c6dbf88e --- /dev/null +++ b/test/src/services/auth-forced-password.test.js @@ -0,0 +1,173 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const { decodeJwt } = require('jose') +const { + snapshotOidcEnv, + DEFAULT_TEST_PASSWORD, + createEmbeddedAuthHarness, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') + +describe('Forced password change (CLI + RBAC)', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + + beforeEach(async () => { + await $harness + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + it('issues access tokens with password_change_required for temp passwords', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'temp@example.com', + groupNames: ['viewer'], + mustChangePassword: true + }) + + const result = await modules.UserService.login({ + email: 'temp@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const claims = decodeJwt(result.accessToken) + expect(claims.password_change_required).to.equal(true) + }) + + it('returns 403 on protected routes until password is changed', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'temp@example.com', + groupNames: ['viewer'], + mustChangePassword: true + }) + + const loginResult = await modules.UserService.login({ + email: 'temp@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const rbacMiddleware = require('../../../src/lib/rbac/middleware') + const callback = $sandbox.spy() + const req = { + method: 'GET', + path: '/api/v3/application', + kauth: { + grant: { + access_token: { + content: decodeJwt(loginResult.accessToken) + } + } + } + } + const res = { + statusCode: null, + body: null, + status (code) { + this.statusCode = code + return this + }, + json (payload) { + this.body = payload + return this + } + } + + await rbacMiddleware.protect()(req, res, callback) + + expect(res.statusCode).to.equal(403) + expect(callback).to.not.have.been.called + }) + + it('allows profile and change-password, then restores full access', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'temp@example.com', + groupNames: ['viewer'], + mustChangePassword: true + }) + + const loginResult = await modules.UserService.login({ + email: 'temp@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const authorizer = require('../../../src/lib/rbac/authorizer') + $sandbox.stub(authorizer, 'authorize').resolves({ allowed: true }) + + const rbacMiddleware = require('../../../src/lib/rbac/middleware') + const profileReq = { + method: 'GET', + path: '/api/v3/user/profile', + headers: { + authorization: `Bearer ${loginResult.accessToken}` + }, + kauth: { + grant: { + access_token: { + content: decodeJwt(loginResult.accessToken) + } + } + } + } + const profileRes = { + statusCode: null, + body: null, + status (code) { + this.statusCode = code + return this + }, + json (payload) { + this.body = payload + return this + } + } + const profileCallback = $sandbox.spy() + await rbacMiddleware.protect()(profileReq, profileRes, profileCallback) + expect(profileCallback).to.have.been.calledOnce + + await modules.AuthUserService.changePasswordWithCurrent( + profileReq.kauth.grant.access_token.content.sub, + DEFAULT_TEST_PASSWORD, + 'NewSecurePass456!' + ) + + const protectedReq = { + method: 'GET', + path: '/api/v3/application', + kauth: profileReq.kauth + } + const protectedRes = { + statusCode: null, + body: null, + status (code) { + this.statusCode = code + return this + }, + json (payload) { + this.body = payload + return this + } + } + const protectedCallback = $sandbox.spy() + await rbacMiddleware.protect()(protectedReq, protectedRes, protectedCallback) + expect(protectedCallback).to.have.been.calledOnce + }) + + it('sets mustChangePassword true when admin creates a user with password', async () => { + const { modules } = await $harness + + const created = await modules.AuthUserService.createUser({ + email: 'new-user@example.com', + password: DEFAULT_TEST_PASSWORD, + groups: ['developer'] + }) + + expect(created.mustChangePassword).to.equal(true) + }) +}) diff --git a/test/src/services/auth-integration.test.js b/test/src/services/auth-integration.test.js index c9167ff5..5667be3a 100644 --- a/test/src/services/auth-integration.test.js +++ b/test/src/services/auth-integration.test.js @@ -42,6 +42,8 @@ describe('Embedded auth integration', () => { expect(result.accessToken).to.be.a('string').that.is.not.empty expect(result.refreshToken).to.be.a('string').that.is.not.empty + expect(result.accessToken.split('.')).to.have.length(3) + expect(result.refreshToken.split('.')).to.have.length(3) }) it('issues tokens for admin users with MFA when totp is provided', async () => { @@ -63,6 +65,8 @@ describe('Embedded auth integration', () => { expect(result.accessToken).to.be.a('string').that.is.not.empty expect(result.refreshToken).to.be.a('string').that.is.not.empty + expect(result.accessToken.split('.')).to.have.length(3) + expect(result.refreshToken.split('.')).to.have.length(3) }) it('rejects admin login without totp when MFA is enabled', async () => { @@ -88,18 +92,20 @@ describe('Embedded auth integration', () => { it('allows bootstrap admin login without MFA', async () => { const { store, modules } = await $harness await store.seedUser({ - email: 'bootstrap@example.com', + email: 'admin', groupNames: ['admin'], isBootstrap: true }) const result = await modules.UserService.login({ - email: 'bootstrap@example.com', + email: 'admin', password: DEFAULT_TEST_PASSWORD }, false) expect(result.accessToken).to.be.a('string').that.is.not.empty expect(result.refreshToken).to.be.a('string').that.is.not.empty + expect(result.accessToken.split('.')).to.have.length(3) + expect(result.refreshToken.split('.')).to.have.length(3) }) it('rejects non-bootstrap admin login when MFA is not enrolled', async () => { @@ -138,6 +144,8 @@ describe('Embedded auth integration', () => { expect(refreshResult.accessToken).to.be.a('string').that.is.not.empty expect(refreshResult.refreshToken).to.be.a('string').that.is.not.empty + expect(refreshResult.accessToken.split('.')).to.have.length(3) + expect(refreshResult.refreshToken.split('.')).to.have.length(3) expect(refreshResult.refreshToken).to.not.equal(loginResult.refreshToken) }) @@ -176,6 +184,7 @@ describe('Embedded auth integration', () => { expect(created.email).to.equal('new-user@example.com') expect(created.groups).to.deep.equal(['developer']) + expect(created.mustChangePassword).to.equal(true) const listed = await modules.AuthUserService.listUsers() expect(listed.map((user) => user.email)).to.include('new-user@example.com') diff --git a/test/src/services/auth-token-service.test.js b/test/src/services/auth-token-service.test.js new file mode 100644 index 00000000..ae008863 --- /dev/null +++ b/test/src/services/auth-token-service.test.js @@ -0,0 +1,78 @@ +'use strict' + +const { expect } = require('chai') +const { decodeJwt } = require('jose') +const sinon = require('sinon') +const { + snapshotOidcEnv, + DEFAULT_TEST_PASSWORD, + createEmbeddedAuthHarness, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') +const AuthTokenService = require('../../../src/services/auth-token-service') + +function expectJwtShape (token) { + expect(token).to.be.a('string') + expect(token.split('.')).to.have.length(3) + expect(token.startsWith('eyJ')).to.equal(true) +} + +describe('Auth token service', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + + beforeEach(async () => { + await $harness + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + it('issues JWT access and refresh tokens from login', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const result = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + expectJwtShape(result.accessToken) + expectJwtShape(result.refreshToken) + + const accessClaims = decodeJwt(result.accessToken) + const refreshClaims = decodeJwt(result.refreshToken) + + expect(accessClaims.token_use).to.equal(AuthTokenService.ACCESS_TOKEN_USE) + expect(refreshClaims.token_use).to.equal(AuthTokenService.REFRESH_TOKEN_USE) + expect(refreshClaims.jti).to.be.a('string').that.is.not.empty + expect(refreshClaims.family_id).to.be.a('string').that.is.not.empty + }) + + it('rotates JWT refresh tokens', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const loginResult = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const refreshResult = await modules.UserService.refresh({ + refreshToken: loginResult.refreshToken + }, false) + + expectJwtShape(refreshResult.accessToken) + expectJwtShape(refreshResult.refreshToken) + expect(refreshResult.refreshToken).to.not.equal(loginResult.refreshToken) + }) +}) From 85c0c2da7bcd83dadb7bdf41c1c49d623aab22b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 17:04:37 +0300 Subject: [PATCH 52/75] Wire auth session startup and address auth groups by unique name. Resolve BFF session secret before mounting middleware, emit controller config for embedded auth mode, and change group CRUD paths to /groups/:name. --- src/config/rbac-resources.yaml | 25 +++- src/controllers/users-controller.js | 10 +- src/routes/users.js | 6 +- src/server.js | 125 ++++++++++++------ src/services/auth-user-service.js | 84 +++++++----- .../services/auth-user-service-groups.test.js | 99 ++++++++++++++ 6 files changed, 270 insertions(+), 79 deletions(-) create mode 100644 test/src/services/auth-user-service-groups.test.js diff --git a/src/config/rbac-resources.yaml b/src/config/rbac-resources.yaml index adbaaa81..91f2f60b 100644 --- a/src/config/rbac-resources.yaml +++ b/src/config/rbac-resources.yaml @@ -684,6 +684,27 @@ resources: - path: /api/v3/user/oauth/callback methods: GET: [] + - path: /api/v3/user/interaction/:uid + methods: + GET: [] + - path: /api/v3/user/interaction/:uid/login + methods: + POST: [] + - path: /api/v3/user/interaction/:uid/mfa + methods: + POST: [] + - path: /api/v3/user/interaction/:uid/enroll + methods: + POST: [] + - path: /api/v3/user/interaction/:uid/confirm-enroll + methods: + POST: [] + - path: /api/v3/user/interaction/:uid/change-password + methods: + POST: [] + - path: /api/v3/user/interaction/:uid/complete + methods: + POST: [] authAdmin: basePath: /api/v3/auth @@ -724,12 +745,12 @@ resources: methods: GET: [list] POST: [create] - - path: /api/v3/groups/:id + - path: /api/v3/groups/:name methods: GET: [get] PATCH: [patch] DELETE: [delete] - resourceNameParam: id + resourceNameParam: name # Config management config: diff --git a/src/controllers/users-controller.js b/src/controllers/users-controller.js index ab9ccb8f..234ee902 100644 --- a/src/controllers/users-controller.js +++ b/src/controllers/users-controller.js @@ -63,20 +63,24 @@ const createGroupEndPoint = async function (req) { return AuthUserService.createGroup(req.body) } +function getGroupNameParam (req) { + return decodeURIComponent(req.params.name || '') +} + const getGroupEndPoint = async function (req) { AuthUserService.ensureEmbeddedMode() - return AuthUserService.getGroup(parseInt(req.params.id, 10)) + return AuthUserService.getGroup(getGroupNameParam(req)) } const updateGroupEndPoint = async function (req) { AuthUserService.ensureEmbeddedMode() await Validator.validate(req.body, Validator.schemas.updateAuthGroup) - return AuthUserService.updateGroup(parseInt(req.params.id, 10), req.body) + return AuthUserService.updateGroup(getGroupNameParam(req), req.body) } const deleteGroupEndPoint = async function (req) { AuthUserService.ensureEmbeddedMode() - return AuthUserService.deleteGroup(parseInt(req.params.id, 10)) + return AuthUserService.deleteGroup(getGroupNameParam(req)) } module.exports = { diff --git a/src/routes/users.js b/src/routes/users.js index d77c3a9d..8810fdf9 100644 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -123,17 +123,17 @@ module.exports = [ }, { method: 'get', - path: '/api/v3/groups/:id', + path: '/api/v3/groups/:name', middleware: protectEmbeddedRoute(UsersController.getGroupEndPoint, constants.HTTP_CODE_SUCCESS) }, { method: 'patch', - path: '/api/v3/groups/:id', + path: '/api/v3/groups/:name', middleware: protectEmbeddedRoute(UsersController.updateGroupEndPoint, constants.HTTP_CODE_SUCCESS) }, { method: 'delete', - path: '/api/v3/groups/:id', + path: '/api/v3/groups/:name', middleware: protectEmbeddedRoute(UsersController.deleteGroupEndPoint, constants.HTTP_CODE_NO_CONTENT) } ] diff --git a/src/server.js b/src/server.js index ac23551c..e734a60d 100755 --- a/src/server.js +++ b/src/server.js @@ -39,9 +39,15 @@ initialize().then(() => { // Initialize session and OIDC bearer validation after config is loaded const session = require('express-session') - const { initOidc, getOidcMiddleware, getMemoryStore, getAuthMode, isAuthConfigured } = require('./config/oidc.js') - const memoryStore = getMemoryStore() - initOidc() + const { initOidc, getOidcMiddleware, getAuthMode, isAuthConfigured } = require('./config/oidc.js') + const { + initAuthSessionStore, + getAuthSessionStore, + getSessionSecret, + getSessionStoreConfig, + resolveSessionSecret + } = require('./config/auth-session-store.js') + const { getPublicUrl, getViewerUrl } = require('./config/auth-urls.js') const viewerApp = express() const app = express() @@ -79,49 +85,69 @@ initialize().then(() => { validateProductionPublicUrl() - app.use(cors()) + const devMode = process.env.DEV_MODE || config.get('server.devMode', true) + const insecureAllowHttp = config.get('auth.insecureAllowHttp', false) + + const viewerURLForCors = getViewerUrl() + app.use(cors({ + origin (origin, callback) { + if (!origin || !viewerURLForCors) { + callback(null, true) + return + } + callback(null, origin === viewerURLForCors) + }, + credentials: true + })) app.use(helmet()) app.use(xss()) // express logs // app.use(morgan('combined')); - app.use(session({ - secret: 'pot-controller', - resave: false, - saveUninitialized: true, - store: memoryStore - })) - app.use(getOidcMiddleware()) - app.use(bodyParser.urlencoded({ - extended: true - })) - app.use(bodyParser.json()) + const sessionStoreConfig = getSessionStoreConfig() - app.engine('ejs', renderFile) - app.set('view engine', 'ejs') - app.use(cookieParser()) + function skipOidcPaths (middleware) { + return (req, res, next) => { + if ((req.path || '').startsWith('/oidc')) { + return next() + } + return middleware(req, res, next) + } + } - app.set('views', path.join(__dirname, 'views')) + function registerApiMiddleware () { + app.use(skipOidcPaths(bodyParser.urlencoded({ + extended: true + }))) + app.use(skipOidcPaths(bodyParser.json())) - app.on('uncaughtException', (req, res, route, err) => { - // TODO - }) + app.engine('ejs', renderFile) + app.set('view engine', 'ejs') + app.use(cookieParser()) - app.use((req, res, next) => { - if (req.headers && req.headers['request-id']) { - req.id = req.headers['request-id'] - delete req.headers['request-id'] - } + app.set('views', path.join(__dirname, 'views')) + + app.on('uncaughtException', (req, res, route, err) => { + // TODO + }) + + app.use((req, res, next) => { + if (req.headers && req.headers['request-id']) { + req.id = req.headers['request-id'] + delete req.headers['request-id'] + } + + res.append('X-Timestamp', Date.now()) + next() + }) - res.append('X-Timestamp', Date.now()) - next() - }) + const { authRateLimitMiddleware } = require('./middlewares/auth-rate-limit-middleware') + app.use(authRateLimitMiddleware) - // Event audit middleware - tracks non-GET operations - // Must be after authentication middleware but before route handlers - const eventAuditMiddleware = require('./middlewares/event-audit-middleware') - app.use(eventAuditMiddleware) + const eventAuditMiddleware = require('./middlewares/event-audit-middleware') + app.use(eventAuditMiddleware) + } global.appRoot = path.resolve(__dirname) @@ -227,12 +253,11 @@ initialize().then(() => { } } - const devMode = process.env.DEV_MODE || config.get('server.devMode') const apiPort = process.env.API_PORT || config.get('server.port') const viewerPort = process.env.VIEWER_PORT || config.get('viewer.port') - const viewerURL = process.env.VIEWER_URL || config.get('viewer.url') const controlPlane = process.env.CONTROL_PLANE || config.get('app.ControlPlane') - const publicUrl = (process.env.CONTROLLER_PUBLIC_URL || config.get('server.publicUrl') || '').replace(/\/$/, '') + const publicUrl = getPublicUrl() + const viewerURL = getViewerUrl() // File-based TLS configuration const tlsKey = process.env.TLS_PATH_KEY || config.get('server.tls.path.key') @@ -292,12 +317,14 @@ initialize().then(() => { const ecnViewerControllerConfig = { apiPort, auth: { + mode: getAuthMode(), loginUrl: '/api/v3/user/login', refreshUrl: '/api/v3/user/refresh', logoutUrl: '/api/v3/user/logout', profileUrl: '/api/v3/user/profile', changePasswordUrl: '/api/v3/user/change-password', - oauthAuthorizeUrl: '/api/v3/user/oauth/authorize' + oauthAuthorizeUrl: '/api/v3/user/oauth/authorize', + oauthInteractionUrl: '/login/oauth' } } if (publicUrl) { @@ -315,7 +342,27 @@ initialize().then(() => { fs.writeFileSync(ecnViewerControllerConfigFilePath, ecnViewerConfigScript) } - initState() + resolveSessionSecret() + .then(() => { + initOidc() + initAuthSessionStore() + + app.use(session({ + secret: getSessionSecret(), + resave: false, + saveUninitialized: false, + store: getAuthSessionStore(), + cookie: { + maxAge: sessionStoreConfig.ttlMs, + sameSite: 'lax', + secure: !devMode && !insecureAllowHttp + } + })) + app.use(getOidcMiddleware()) + registerApiMiddleware() + + return initState() + }) .then(() => { if (hasFileBasedTLS) { startHttpsServer( diff --git a/src/services/auth-user-service.js b/src/services/auth-user-service.js index 22a12842..46db8c17 100644 --- a/src/services/auth-user-service.js +++ b/src/services/auth-user-service.js @@ -199,7 +199,7 @@ async function createUser ({ email, password, groups }, transaction) { id: userId, email: normalizedEmail, passwordHash: await AuthPasswordService.hashPassword(password), - mustChangePassword: false, + mustChangePassword: true, isBootstrap: false }, withTransaction(transaction)) @@ -324,6 +324,24 @@ async function consumeResetToken (resetToken, transaction) { return session.userId } +async function changePasswordWithCurrent (userId, currentPassword, newPassword, transaction) { + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + + if (!currentPassword || !newPassword) { + throw new Errors.ValidationError('currentPassword and newPassword are required') + } + + if (!await AuthPasswordService.verifyPassword(currentPassword, user.passwordHash)) { + throw new Errors.InvalidCredentialsError() + } + + await updatePassword(user, newPassword, transaction) + return { status: 'success' } +} + async function changePassword (req, payload, transaction) { if (payload.resetToken) { const userId = await consumeResetToken(payload.resetToken, transaction) @@ -365,20 +383,36 @@ async function listGroups (transaction) { order: [['name', 'ASC']] })) - return groups.map((group) => ({ + return groups.map(formatGroupResponse) +} + +function normalizeGroupName (name) { + const normalizedName = String(name || '').trim().toLowerCase() + if (!normalizedName) { + throw new Errors.ValidationError('name is required') + } + return normalizedName +} + +async function findGroupByName (groupName, transaction) { + const normalizedName = normalizeGroupName(groupName) + return db.AuthGroup.findOne(withTransaction(transaction, { + where: { name: normalizedName } + })) +} + +function formatGroupResponse (group) { + return { id: group.id, name: group.name, isSystem: group.isSystem, createdAt: group.createdAt, updatedAt: group.updatedAt - })) + } } async function createGroup ({ name }, transaction) { - const normalizedName = String(name || '').trim().toLowerCase() - if (!normalizedName) { - throw new Errors.ValidationError('name is required') - } + const normalizedName = normalizeGroupName(name) if (SYSTEM_GROUP_NAMES.includes(normalizedName)) { throw new Errors.ConflictError('A system group with this name already exists') } @@ -395,32 +429,20 @@ async function createGroup ({ name }, transaction) { isSystem: false }, withTransaction(transaction)) - return { - id: group.id, - name: group.name, - isSystem: group.isSystem, - createdAt: group.createdAt, - updatedAt: group.updatedAt - } + return formatGroupResponse(group) } -async function getGroup (groupId, transaction) { - const group = await db.AuthGroup.findByPk(groupId, withTransaction(transaction)) +async function getGroup (groupName, transaction) { + const group = await findGroupByName(groupName, transaction) if (!group) { throw new Errors.NotFoundError('Group not found') } - return { - id: group.id, - name: group.name, - isSystem: group.isSystem, - createdAt: group.createdAt, - updatedAt: group.updatedAt - } + return formatGroupResponse(group) } -async function updateGroup (groupId, payload, transaction) { - const group = await db.AuthGroup.findByPk(groupId, withTransaction(transaction)) +async function updateGroup (groupName, payload, transaction) { + const group = await findGroupByName(groupName, transaction) if (!group) { throw new Errors.NotFoundError('Group not found') } @@ -428,10 +450,7 @@ async function updateGroup (groupId, payload, transaction) { throw new Errors.ForbiddenError('System groups cannot be modified') } - const normalizedName = String(payload.name || '').trim().toLowerCase() - if (!normalizedName) { - throw new Errors.ValidationError('name is required') - } + const normalizedName = normalizeGroupName(payload.name) const duplicate = await db.AuthGroup.findOne(withTransaction(transaction, { where: { @@ -444,11 +463,11 @@ async function updateGroup (groupId, payload, transaction) { } await group.update({ name: normalizedName }, withTransaction(transaction)) - return getGroup(group.id, transaction) + return getGroup(normalizedName, transaction) } -async function deleteGroup (groupId, transaction) { - const group = await db.AuthGroup.findByPk(groupId, withTransaction(transaction)) +async function deleteGroup (groupName, transaction) { + const group = await findGroupByName(groupName, transaction) if (!group) { throw new Errors.NotFoundError('Group not found') } @@ -471,6 +490,7 @@ module.exports = { deleteUser: TransactionDecorator.generateTransaction(deleteUser), resetPassword: TransactionDecorator.generateTransaction(resetPassword), resetToken: TransactionDecorator.generateTransaction(resetToken), + changePasswordWithCurrent: TransactionDecorator.generateTransaction(changePasswordWithCurrent), changePassword: TransactionDecorator.generateTransaction(changePassword), listGroups: TransactionDecorator.generateTransaction(listGroups), createGroup: TransactionDecorator.generateTransaction(createGroup), diff --git a/test/src/services/auth-user-service-groups.test.js b/test/src/services/auth-user-service-groups.test.js new file mode 100644 index 00000000..b9d83273 --- /dev/null +++ b/test/src/services/auth-user-service-groups.test.js @@ -0,0 +1,99 @@ +'use strict' + +const { expect } = require('chai') +const sinon = require('sinon') +const Errors = require('../../../src/helpers/errors') + +function stubModelMethod (db, modelName, methodName, sandbox, impl) { + if (!db[modelName]) { + db[modelName] = {} + } + db[modelName][methodName] = sandbox.stub().callsFake(impl) +} + +function createGroupRecord (data) { + const record = { + id: 4, + name: 'viewer', + isSystem: true, + createdAt: new Date('2026-01-01T00:00:00Z'), + updatedAt: new Date('2026-01-01T00:00:00Z'), + ...data + } + + record.update = async function (fields) { + Object.assign(this, fields) + return this + } + + record.destroy = async function () { + record._deleted = true + return undefined + } + + return record +} + +function reloadAuthUserService () { + delete require.cache[require.resolve('../../../src/services/auth-user-service')] + return require('../../../src/services/auth-user-service') +} + +describe('auth-user-service groups by name', () => { + def('sandbox', () => sinon.createSandbox()) + def('viewerGroup', () => createGroupRecord({ id: 4, name: 'viewer', isSystem: true })) + + afterEach(() => { + $sandbox.restore() + delete require.cache[require.resolve('../../../src/services/auth-user-service')] + }) + + it('gets a group by name', async () => { + const db = require('../../../src/data/models') + stubModelMethod(db, 'AuthGroup', 'findOne', $sandbox, async () => $viewerGroup) + + const { getGroup } = reloadAuthUserService() + const result = await getGroup('viewer') + + expect(result).to.deep.include({ id: 4, name: 'viewer', isSystem: true }) + expect(db.AuthGroup.findOne.firstCall.args[0].where).to.deep.equal({ name: 'viewer' }) + }) + + it('normalizes group name lookup to lowercase', async () => { + const db = require('../../../src/data/models') + stubModelMethod(db, 'AuthGroup', 'findOne', $sandbox, async () => $viewerGroup) + + const { getGroup } = reloadAuthUserService() + await getGroup('Viewer') + + expect(db.AuthGroup.findOne.firstCall.args[0].where).to.deep.equal({ name: 'viewer' }) + }) + + it('returns 404 when group name is not found', async () => { + const db = require('../../../src/data/models') + stubModelMethod(db, 'AuthGroup', 'findOne', $sandbox, async () => null) + + const { getGroup } = reloadAuthUserService() + + try { + await getGroup('missing-group') + expect.fail('expected not found') + } catch (error) { + expect(error).to.be.instanceOf(Errors.NotFoundError) + } + }) + + it('rejects deleting system groups by name', async () => { + const db = require('../../../src/data/models') + stubModelMethod(db, 'AuthGroup', 'findOne', $sandbox, async () => $viewerGroup) + + const { deleteGroup } = reloadAuthUserService() + + try { + await deleteGroup('viewer') + expect.fail('expected forbidden') + } catch (error) { + expect(error).to.be.instanceOf(Errors.ForbiddenError) + } + }) +}) From 61c2f4e311b02657a669d1562072d58bf14b6b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 17:04:42 +0300 Subject: [PATCH 53/75] Update API spec, auth configuration samples, and OIDC test harness. Document interaction routes, auth groups by name, session store and TTL settings in swagger and config, and extend embedded OIDC smoke and unit test coverage. --- docs/swagger.yaml | 431 ++++++++++++++++++++++++-- src/config/config.yaml | 20 +- src/config/env-mapping.js | 14 +- test/oidc/README.md | 91 +++++- test/oidc/run-embedded-smoke.js | 11 +- test/src/config/oidc.test.js | 113 +++++++ test/support/embedded-auth-harness.js | 7 +- test/support/oidc-test-helpers.js | 6 +- 8 files changed, 664 insertions(+), 29 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 02c24784..b1b3f923 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -3040,6 +3040,14 @@ paths: - User summary: Login operationId: login + description: > + CLI login (potctl). The `email` field is the login identifier (username or email); + bootstrap admin may use a non-email username. User creation via POST /users still + requires a valid email address. + + When the account has a temporary password (`mustChangePassword`), the access token + includes JWT claim `password_change_required: true`. RBAC allows only + `GET /user/profile` and `POST /user/change-password` until the password is changed. requestBody: content: application/json: @@ -3062,6 +3070,8 @@ paths: description: bad request "401": description: incorrect credentials + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" /user/refresh: post: tags: @@ -3154,6 +3164,8 @@ paths: description: Not Authorized "501": description: Embedded auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" /user/mfa/enroll: post: tags: @@ -3174,6 +3186,8 @@ paths: description: Not Authorized "501": description: Embedded auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" /user/mfa/confirm: post: tags: @@ -3202,6 +3216,8 @@ paths: description: Invalid credentials or enrollment token "501": description: Embedded auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" /user/mfa: delete: tags: @@ -3235,16 +3251,40 @@ paths: get: tags: - User - summary: Start external IdP OAuth authorization (BFF) + summary: Start OAuth authorization (BFF) operationId: oauthAuthorize - description: Redirects the browser to the external OIDC authorization endpoint. External auth mode only. + description: > + Browser OAuth BFF entry point for ECN Viewer (Plan 8.2). ECN Viewer Sign in performs a + full-page redirect here; do **not** POST credentials from the browser. + + **External mode:** redirects to the external IdP authorize endpoint. + + **Embedded mode:** redirects to the in-process issuer at `{CONTROLLER_PUBLIC_URL}/oidc/auth`, + which starts an OAuth interaction and redirects the browser to + `{viewerUrl}{auth.oauthInteractionUrl}?interaction=` for Viewer-owned login/MFA steps. + + Redirect chain (external): + + `Viewer → GET /user/oauth/authorize → external IdP (login, MFA, password policy) → + GET /user/oauth/callback → 302 {viewerUrl}/login#accessToken=...&refreshToken=...` + + Redirect chain (embedded): + + `Viewer → GET /user/oauth/authorize → /oidc/auth → {viewerUrl}/login/oauth?interaction= + → interaction APIs → GET /user/oauth/callback → 302 {viewerUrl}/login#tokens` + + OAuth callback redirect URI: `{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback`. + Requires `VIEWER_URL` in embedded mode. External mode requires `OIDC_ISSUER_URL`, + `OIDC_CLIENT_ID`, and `OIDC_CLIENT_SECRET` for the confidential Controller client. responses: "302": - description: Redirect to external IdP + description: Redirect to IdP or embedded issuer authorization endpoint "401": - description: Not Authorized + description: Auth not configured or OAuth session error "501": - description: External auth mode only + description: Embedded OAuth BFF requires VIEWER_URL + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" /user/oauth/callback: get: tags: @@ -3252,22 +3292,268 @@ paths: summary: OAuth authorization callback (BFF) operationId: oauthCallback description: > - Exchanges the authorization code for tokens. When VIEWER_URL is configured, - redirects to `{viewerUrl}/login#accessToken=...&refreshToken=...`. Otherwise - returns JSON `{ accessToken, refreshToken }`. + Exchanges the authorization code for tokens using the confidential Controller OIDC client. + Validates `state` and `nonce` from the server session (10-minute TTL). Works in **embedded** + and **external** auth modes. + + When `VIEWER_URL` is configured (recommended for browser login), responds with **302** to + `{viewerUrl}/login#accessToken=...&refreshToken=...`. Viewer `/login` parses the URL + fragment, stores tokens, clears the hash, and navigates to the app. When `VIEWER_URL` is + unset, returns JSON **200** `{ accessToken, refreshToken }` (CLI/automation only). + + **External forced password change** is enforced by the IdP during authorize (e.g. Keycloak + required actions: `UPDATE_PASSWORD`). **Embedded forced password** uses the interaction + `change-password` step (Plan 8.2-3). + + MFA in external browser login is IdP-owned; embedded browser MFA uses interaction endpoints. responses: "200": - description: Token response (when VIEWER_URL is not configured) + description: Token response when VIEWER_URL is not configured content: application/json: schema: $ref: "#/components/schemas/LoginSuccessResponse" "302": - description: Redirect to ECN Viewer login with tokens in URL fragment + description: Redirect to ECN Viewer `/login` with tokens in URL fragment + "401": + description: OAuth session expired, state/nonce mismatch, or authorization code exchange failed + "501": + description: Embedded OAuth BFF requires VIEWER_URL + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/interaction/{uid}: + get: + tags: + - User + summary: Get embedded OAuth interaction status + operationId: interactionStatus + description: > + **Embedded auth mode only.** Returns the next interaction step for the OAuth BFF flow. + Unauthenticated — the interaction uid comes from the Viewer redirect query string + (`?interaction=`). Steps: `login`, `mfa`, `enroll`, `confirm-enroll`, + `change-password`, `complete`. + parameters: + - name: uid + in: path + required: true + schema: + type: string + responses: + "200": + description: Current interaction step + content: + application/json: + schema: + $ref: "#/components/schemas/InteractionStepResponse" + "401": + description: Interaction session not found or expired + "501": + description: External auth mode only (embedded interactions) + /user/interaction/{uid}/login: + post: + tags: + - User + summary: Submit credentials for embedded OAuth interaction + operationId: interactionLogin + description: > + **Embedded auth mode only.** Verifies email/login identifier and password for the OAuth + interaction. Returns the next step on success. Failures return **401** (no custom MFA JSON). + parameters: + - name: uid + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InteractionLoginRequest" + responses: + "200": + description: Credentials accepted; returns next step + content: + application/json: + schema: + $ref: "#/components/schemas/InteractionStepResponse" + "400": + description: Validation error + "401": + description: Invalid credentials or interaction expired + "501": + description: External auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/interaction/{uid}/mfa: + post: + tags: + - User + summary: Submit TOTP for embedded OAuth interaction + operationId: interactionMfa + description: > + **Embedded auth mode only.** Verifies TOTP for admin users with MFA enabled during the + OAuth interaction. Returns the next step on success. + parameters: + - name: uid + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InteractionMfaRequest" + responses: + "200": + description: MFA verified; returns next step + content: + application/json: + schema: + $ref: "#/components/schemas/InteractionStepResponse" + "400": + description: Validation error + "401": + description: Invalid MFA code or interaction expired + "501": + description: External auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/interaction/{uid}/enroll: + post: + tags: + - User + summary: Start MFA enrollment during embedded OAuth interaction + operationId: interactionEnroll + description: > + **Embedded auth mode only.** Starts admin MFA enrollment during the browser OAuth + interaction (browser-only first enrollment). Returns TOTP secret material for QR display. + parameters: + - name: uid + in: path + required: true + schema: + type: string + responses: + "200": + description: Enrollment started + content: + application/json: + schema: + $ref: "#/components/schemas/InteractionEnrollResponse" + "400": + description: Validation error + "401": + description: Interaction expired or credentials not verified + "501": + description: External auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/interaction/{uid}/confirm-enroll: + post: + tags: + - User + summary: Confirm MFA enrollment during embedded OAuth interaction + operationId: interactionConfirmEnroll + description: > + **Embedded auth mode only.** Confirms MFA enrollment with a TOTP code during the browser + OAuth interaction. Returns recovery codes on success. + parameters: + - name: uid + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InteractionMfaRequest" + responses: + "200": + description: MFA enrollment confirmed + content: + application/json: + schema: + $ref: "#/components/schemas/InteractionConfirmEnrollResponse" + "400": + description: Validation error + "401": + description: Invalid code or interaction expired + "501": + description: External auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/interaction/{uid}/change-password: + post: + tags: + - User + summary: Forced password change during embedded OAuth interaction + operationId: interactionChangePassword + description: > + **Embedded auth mode only.** Forced password change step before OAuth tokens are issued + when `mustChangePassword` is true. Requires `currentPassword` and `newPassword` in the body. + parameters: + - name: uid + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ChangePasswordRequest" + responses: + "200": + description: Password changed; returns next step + content: + application/json: + schema: + $ref: "#/components/schemas/InteractionStepResponse" + "400": + description: Validation error + "401": + description: Invalid credentials or interaction expired + "501": + description: External auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/interaction/{uid}/complete: + post: + tags: + - User + summary: Finish embedded OAuth interaction + operationId: interactionComplete + description: > + **Embedded auth mode only.** Completes the oidc-provider interaction after all required + steps are satisfied. Returns `{ redirectTo }` — Viewer should navigate the browser to + this URL to resume the OAuth authorize flow and reach `/user/oauth/callback`. + parameters: + - name: uid + in: path + required: true + schema: + type: string + responses: + "200": + description: Interaction finished; resume OAuth at redirectTo + content: + application/json: + schema: + $ref: "#/components/schemas/InteractionCompleteResponse" + "400": + description: Required interaction step not yet completed "401": - description: OAuth session invalid or authorization failed + description: Interaction expired or credentials invalid "501": description: External auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" /auth/migration/export: post: tags: @@ -3340,6 +3626,9 @@ paths: - Auth Users summary: Create embedded auth user operationId: createAuthUser + description: > + Admin-created users receive `mustChangePassword: true` so the first login forces + a password change (browser interaction step or CLI JWT `password_change_required` gate). security: - authToken: [] requestBody: @@ -3551,7 +3840,7 @@ paths: description: Conflict "501": description: Embedded auth mode only - "/groups/{id}": + "/groups/{name}": get: tags: - Auth Groups @@ -3560,11 +3849,12 @@ paths: security: - authToken: [] parameters: - - name: id + - name: name in: path required: true schema: - type: integer + type: string + description: Unique auth group name (e.g. viewer, platform-ops) responses: "200": description: Success @@ -3586,11 +3876,12 @@ paths: security: - authToken: [] parameters: - - name: id + - name: name in: path required: true schema: - type: integer + type: string + description: Current auth group name requestBody: required: true content: @@ -3622,11 +3913,12 @@ paths: security: - authToken: [] parameters: - - name: id + - name: name in: path required: true schema: - type: integer + type: string + description: Auth group name responses: "204": description: Deleted @@ -6452,6 +6744,18 @@ components: type: http scheme: bearer bearerFormat: JWT + responses: + AuthRateLimitExceeded: + description: Too many authentication requests from this IP address + headers: + Retry-After: + description: Seconds until the current rate-limit window resets + schema: + type: integer + content: + application/json: + schema: + $ref: "#/components/schemas/RateLimitExceededResponse" requestBodies: UpdateIOFogNodeRequestBody: content: @@ -8493,6 +8797,8 @@ components: properties: email: type: string + minLength: 1 + description: Login identifier (email for normal users; bootstrap admin may use a non-email username) password: type: string totp: @@ -8536,8 +8842,82 @@ components: properties: accessToken: type: string + description: > + JWT access token (`token_use=access`). Includes `password_change_required: true` + when the account must change its password before full API access. refreshToken: type: string + description: JWT refresh token (`token_use=refresh`). Use only with POST /user/refresh. + InteractionStepResponse: + type: object + required: + - step + properties: + step: + type: string + enum: + - login + - mfa + - enroll + - confirm-enroll + - change-password + - complete + InteractionLoginRequest: + type: object + required: + - email + - password + properties: + email: + type: string + description: Login identifier (not required to be an email address) + password: + type: string + InteractionMfaRequest: + type: object + required: + - code + properties: + code: + type: string + InteractionEnrollResponse: + type: object + required: + - step + - secret + - otpauthUrl + properties: + step: + type: string + secret: + type: string + otpauthUrl: + type: string + InteractionConfirmEnrollResponse: + type: object + required: + - step + - recoveryCodes + properties: + step: + type: string + recoveryCodes: + type: array + items: + type: string + InteractionCompleteResponse: + type: object + required: + - redirectTo + - step + properties: + redirectTo: + type: string + description: OAuth resume URL; navigate browser here after all steps complete + step: + type: string + enum: + - complete RefreshSuccessResponse: type: object required: @@ -8573,6 +8953,9 @@ components: type: string email: type: string + password_change_required: + type: boolean + description: Present when JWT claim `password_change_required` is true ChangePasswordRequest: type: object required: @@ -8589,6 +8972,18 @@ components: properties: status: type: string + RateLimitExceededResponse: + type: object + required: + - name + - message + properties: + name: + type: string + example: RateLimitExceededError + message: + type: string + example: Too many authentication requests from this IP address AuthUserCreateRequest: type: object required: diff --git a/src/config/config.yaml b/src/config/config.yaml index f8043314..478ffbcd 100644 --- a/src/config/config.yaml +++ b/src/config/config.yaml @@ -40,7 +40,7 @@ server: # Viewer Configuration viewer: port: 8008 # Viewer port number - url: "" # Viewer URL + url: "" # Viewer URL (VIEWER_URL); defaults to server.publicUrl when empty # Logging Configuration log: @@ -100,7 +100,7 @@ database: # mode: embedded # embedded | external (AUTH_MODE); resolution # insecureAllowHttp: false # Allow http CONTROLLER_PUBLIC_URL in prod (AUTH_INSECURE_ALLOW_HTTP) # bootstrap: -# adminEmail: "" # Bootstrap admin email (OIDC_BOOTSTRAP_ADMIN_EMAIL) +# adminUsername: "" # Bootstrap admin username (OIDC_BOOTSTRAP_ADMIN_USERNAME) # adminPassword: "" # Bootstrap admin password (OIDC_BOOTSTRAP_ADMIN_PASSWORD) # insecureAllowBootstrapLog: false # Allow bootstrap log in prod (AUTH_INSECURE_ALLOW_BOOTSTRAP_LOG) # issuerUrl: # OIDC issuer URL (OIDC_ISSUER_URL); external mode only @@ -108,6 +108,22 @@ database: # id: # Controller API client ID (OIDC_CLIENT_ID) # secret: # Controller API client secret (OIDC_CLIENT_SECRET) # viewerClient: # ECN Viewer SPA client ID (OIDC_VIEWER_CLIENT_ID) +# rateLimit: +# enabled: true # AUTH_RATE_LIMIT_ENABLED +# maxRequestsPerWindow: 60 # AUTH_RATE_LIMIT_MAX_REQUESTS — per-IP auth endpoints +# windowMs: 60000 # AUTH_RATE_LIMIT_WINDOW_MS — sliding window (ms) +# sessionStore: +# type: memory # memory | database (AUTH_SESSION_STORE_TYPE); auto database for mysql/postgres +# ttlMs: 600000 # AUTH_SESSION_STORE_TTL_MS — OAuth BFF session TTL (10 min) +# secret: "" # AUTH_SESSION_SECRET — optional; auto-generated and persisted when unset +# tokenTtl: +# accessTokenTtlSeconds: 900 # AUTH_ACCESS_TOKEN_TTL_SECONDS — runtime override (default from AuthPolicy) +# refreshTokenTtlSeconds: 3600 # AUTH_REFRESH_TOKEN_TTL_SECONDS — runtime override (default 1h from AuthPolicy) +# oidcTtl: +# interactionTtlSeconds: # AUTH_OIDC_INTERACTION_TTL_SECONDS — defaults to sessionStore.ttlMs / 1000 +# grantTtlSeconds: # AUTH_OIDC_GRANT_TTL_SECONDS — defaults to interactionTtlSeconds +# sessionTtlSeconds: # AUTH_OIDC_SESSION_TTL_SECONDS — defaults to AuthPolicy refresh_token_ttl_seconds +# idTokenTtlSeconds: # AUTH_OIDC_ID_TOKEN_TTL_SECONDS — defaults to AuthPolicy access_token_ttl_seconds # Bridge Ports Configuration for Services bridgePorts: diff --git a/src/config/env-mapping.js b/src/config/env-mapping.js index ae7f6055..fa54d872 100644 --- a/src/config/env-mapping.js +++ b/src/config/env-mapping.js @@ -84,9 +84,21 @@ module.exports = { 'OIDC_VIEWER_CLIENT_ID': 'auth.viewerClient', 'AUTH_VIEWER_CLIENT_ENABLED': 'auth.viewerClient.enabled', 'AUTH_INSECURE_ALLOW_HTTP': 'auth.insecureAllowHttp', - 'OIDC_BOOTSTRAP_ADMIN_EMAIL': 'auth.bootstrap.adminEmail', + 'OIDC_BOOTSTRAP_ADMIN_USERNAME': 'auth.bootstrap.adminUsername', 'OIDC_BOOTSTRAP_ADMIN_PASSWORD': 'auth.bootstrap.adminPassword', 'AUTH_INSECURE_ALLOW_BOOTSTRAP_LOG': 'auth.insecureAllowBootstrapLog', + 'AUTH_RATE_LIMIT_ENABLED': 'auth.rateLimit.enabled', + 'AUTH_RATE_LIMIT_MAX_REQUESTS': 'auth.rateLimit.maxRequestsPerWindow', + 'AUTH_RATE_LIMIT_WINDOW_MS': 'auth.rateLimit.windowMs', + 'AUTH_SESSION_STORE_TYPE': 'auth.sessionStore.type', + 'AUTH_SESSION_STORE_TTL_MS': 'auth.sessionStore.ttlMs', + 'AUTH_SESSION_SECRET': 'auth.sessionStore.secret', + 'AUTH_OIDC_INTERACTION_TTL_SECONDS': 'auth.oidcTtl.interactionTtlSeconds', + 'AUTH_OIDC_GRANT_TTL_SECONDS': 'auth.oidcTtl.grantTtlSeconds', + 'AUTH_OIDC_SESSION_TTL_SECONDS': 'auth.oidcTtl.sessionTtlSeconds', + 'AUTH_OIDC_ID_TOKEN_TTL_SECONDS': 'auth.oidcTtl.idTokenTtlSeconds', + 'AUTH_ACCESS_TOKEN_TTL_SECONDS': 'auth.tokenTtl.accessTokenTtlSeconds', + 'AUTH_REFRESH_TOKEN_TTL_SECONDS': 'auth.tokenTtl.refreshTokenTtlSeconds', // Bridge Ports Configuration 'BRIDGE_PORTS_RANGE': 'bridgePorts.range', diff --git a/test/oidc/README.md b/test/oidc/README.md index 248f2348..5d14eee3 100644 --- a/test/oidc/README.md +++ b/test/oidc/README.md @@ -57,12 +57,19 @@ Test harness: `test/support/embedded-auth-harness.js` (in-memory auth store + em npm run start-dev ``` -5. Login (bootstrap admin on first boot): +5. Login (bootstrap admin on first boot; `email` field is the login identifier): ```bash curl -s -X POST http://localhost:51121/api/v3/user/login \ -H "Content-Type: application/json" \ - -d '{"email":"admin@example.com","password":"ChangeMeSecure123!"}' + -d '{"email":"admin","password":"ChangeMeSecure123!"}' + ``` + + Bootstrap env (non-email username supported): + + ```bash + export OIDC_BOOTSTRAP_ADMIN_USERNAME='admin' + export OIDC_BOOTSTRAP_ADMIN_PASSWORD='ChangeMeSecure123!' ``` 6. Call a protected route with the returned `accessToken`: @@ -80,7 +87,85 @@ Point env at any OIDC issuer: - `AUTH_MODE=external` - `OIDC_ISSUER_URL` — full issuer URL - `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` — confidential client -- `CONTROLLER_PUBLIC_URL` — canonical external URL +- `CONTROLLER_PUBLIC_URL` — canonical external URL (issuer host + OAuth callback base) +- `VIEWER_URL` — SPA base; BFF redirects tokens to `{viewerUrl}/login#accessToken=...` + +Optional auth rate limits (Plan 8.2-4): `AUTH_RATE_LIMIT_ENABLED` (default `true`), +`AUTH_RATE_LIMIT_MAX_REQUESTS` (default `60`), `AUTH_RATE_LIMIT_WINDOW_MS` (default `60000`). + +Register at the IdP: redirect URI `{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback`. Embedded-only routes (`/api/v3/users`, migration export, JWKS rotate) return **501** in external mode. + +### External BFF manual checklist (Viewer or curl) + +Prerequisites: external IdP reachable; redirect URI registered; Controller running with session +middleware (default). + +1. **Authorize redirect** — browser or curl with cookies: + + ```bash + curl -sI -c /tmp/oauth-cookies.txt \ + "http://localhost:51121/api/v3/user/oauth/authorize" + ``` + + Expect **302** `Location` pointing at the IdP authorize URL. Embedded mode returns **501**. + +2. **Complete IdP login** in a browser (MFA / forced password handled by IdP, not Controller). + +3. **Callback + Viewer handoff** — after IdP redirects to + `{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback?code=...&state=...`, expect **302** to + `{VIEWER_URL}/login#accessToken=...&refreshToken=...` when `VIEWER_URL` is set. + +4. **Protected API** — copy `accessToken` from the fragment (or JSON **200** when `VIEWER_URL` + is unset) and call: + + ```bash + curl -H "Authorization: Bearer " http://localhost:51121/api/v3/user/profile + ``` + +5. **Negative** — `GET /user/oauth/callback` without a prior authorize session → **401**. + +**Viewer integration:** Sign in button → full-page `GET {apiBase}/user/oauth/authorize`; `/login` +parses hash tokens. See `.cursor/controllerv3.8/docs/08-2-viewer-handoff.md` § External mode. + +## HA BFF sessions (Plan 8.2-5) + +Multi-replica Controller requires a **shared** OAuth BFF session store. Set: + +```bash +export DB_PROVIDER=postgres # or mysql — not sqlite +export AUTH_SESSION_STORE_TYPE=database +export AUTH_SESSION_SECRET='replace-with-shared-secret' +``` + +Embedded interaction step state (`AuthInteractionStates`) uses the same store mode automatically when `AUTH_SESSION_STORE_TYPE=database`. + +### Two-instance manual procedure + +Prerequisites: shared mysql/postgres DB; both instances use identical auth env (`AUTH_MODE`, `CONTROLLER_PUBLIC_URL`, `VIEWER_URL`, `AUTH_SESSION_*`). + +1. Start instance A on port `51121` and instance B on port `51122` (different `SERVER_PORT`). + +2. Begin OAuth on instance A and capture the session cookie: + + ```bash + curl -sI -c /tmp/oauth-ha-cookies.txt \ + "http://localhost:51121/api/v3/user/oauth/authorize" + ``` + + Expect **302** to IdP (external) or embedded `/oidc` (embedded). + +3. Complete login in a browser (or embedded interaction APIs on either instance — interaction state is DB-backed). + +4. When the IdP redirects to `{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback?...`, replay the callback against **instance B** using the cookie from step 2: + + ```bash + curl -sI -b /tmp/oauth-ha-cookies.txt \ + "http://localhost:51122/api/v3/user/oauth/callback?code=&state=" + ``` + + Expect **302** to `{VIEWER_URL}/login#accessToken=...` (or JSON **200** when `VIEWER_URL` is unset). + +5. **Negative control** — with `AUTH_SESSION_STORE_TYPE=memory`, step 4 on a different instance returns **401** (session not found). diff --git a/test/oidc/run-embedded-smoke.js b/test/oidc/run-embedded-smoke.js index 098aa41a..58280a50 100644 --- a/test/oidc/run-embedded-smoke.js +++ b/test/oidc/run-embedded-smoke.js @@ -18,7 +18,7 @@ function main () { console.log(" export AUTH_MODE='embedded'") console.log(` export CONTROLLER_PUBLIC_URL='${EMBEDDED_PUBLIC_URL}'`) console.log(` export OIDC_CLIENT_ID='${EMBEDDED_CLIENT_ID}'`) - console.log(" export OIDC_BOOTSTRAP_ADMIN_EMAIL='admin@example.com'") + console.log(" export OIDC_BOOTSTRAP_ADMIN_USERNAME='admin'") console.log(" export OIDC_BOOTSTRAP_ADMIN_PASSWORD='ChangeMeSecure123!'") console.log('') console.log('Optional for local HTTP smoke:') @@ -30,10 +30,17 @@ function main () { console.log('Login smoke:') console.log(' curl -s -X POST http://localhost:51121/api/v3/user/login \\') console.log(' -H "Content-Type: application/json" \\') - console.log(' -d \'{"email":"admin@example.com","password":"ChangeMeSecure123!"}\'') + console.log(' -d \'{"email":"admin","password":"ChangeMeSecure123!"}\'') console.log('') console.log('Protected route smoke (replace ):') console.log(' curl -H "Authorization: Bearer " http://localhost:51121/api/v3/user/profile') + console.log('') + console.log('Embedded OAuth BFF smoke (requires VIEWER_URL + running Controller):') + console.log(" export VIEWER_URL='http://localhost:8008'") + console.log(' open http://localhost:51121/api/v3/user/oauth/authorize') + console.log(' # → redirects to Viewer /login/oauth?interaction=') + console.log(' # → Viewer calls POST /api/v3/user/interaction/:uid/login (etc.)') + console.log(' # → POST /api/v3/user/interaction/:uid/complete → redirectTo → callback → /login#tokens') } if (require.main === module) { diff --git a/test/src/config/oidc.test.js b/test/src/config/oidc.test.js index 2500b3ff..a8b10888 100644 --- a/test/src/config/oidc.test.js +++ b/test/src/config/oidc.test.js @@ -1,5 +1,6 @@ const { expect } = require('chai') const sinon = require('sinon') +const express = require('express') const config = require('../../../src/config') const { @@ -165,4 +166,116 @@ describe('OIDC config', () => { expect(result.req.kauth).to.equal(undefined) }) }) + + describe('getOauthClientConfiguration()', () => { + function createEmbeddedOidcStubDb () { + const storedKeys = [] + const storedClients = [] + + return { + AuthOidcKey: { + findAll: sinon.stub().resolves(storedKeys), + create: sinon.stub().callsFake(async (values) => { + storedKeys.push(values) + return values + }) + }, + AuthOidcClient: { + findOne: sinon.stub().callsFake(async ({ where }) => { + return storedClients.find((client) => client.clientId === where.clientId) || null + }), + create: sinon.stub().callsFake(async (values) => { + storedClients.push(values) + return values + }) + }, + AuthOidcProviderState: { + upsert: sinon.stub().resolves([{}, true]), + findOne: sinon.stub().resolves(null), + update: sinon.stub().resolves([1]), + destroy: sinon.stub().resolves(0) + }, + AuthPolicy: { + findByPk: sinon.stub().resolves({ + accessTokenTtlSeconds: 900, + refreshTokenTtlSeconds: 604800 + }) + }, + AuthUser: { + findByPk: sinon.stub().resolves(null) + } + } + } + + function reloadEmbeddedOidcModule () { + const embeddedPath = require.resolve('../../../src/config/embedded-oidc') + delete require.cache[embeddedPath] + return require('../../../src/config/embedded-oidc') + } + + async function withEmbeddedIssuerServer (run) { + const app = express() + + const server = await new Promise((resolve, reject) => { + const listener = app.listen(0, '127.0.0.1', () => resolve(listener)) + listener.on('error', reject) + }) + + const { port } = server.address() + const issuerBase = `http://127.0.0.1:${port}` + + applyEmbeddedEnv({ + CONTROLLER_PUBLIC_URL: issuerBase, + OIDC_CLIENT_SECRET: 'embedded-oauth-test-secret' + }) + + const embeddedOidc = reloadEmbeddedOidcModule() + await embeddedOidc.initEmbeddedIssuer(app, { db: createEmbeddedOidcStubDb() }) + + try { + await run(issuerBase, server) + } finally { + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())) + }) + embeddedOidc.resetEmbeddedIssuerForTests() + } + } + + beforeEach(async () => { + await $harness + }) + + it('discovers the embedded issuer over HTTP when auth.insecureAllowHttp is true', async () => { + await withEmbeddedIssuerServer(async (issuerBase) => { + const originalGet = config.get.bind(config) + $sandbox.stub(config, 'get').callsFake((key, defaultValue) => { + if (key === 'auth.insecureAllowHttp') { + return true + } + return originalGet(key, defaultValue) + }) + + const oidc = reloadOidcModule() + oidc.initOidc() + + const clientConfig = await oidc.getOauthClientConfiguration() + expect(clientConfig.serverMetadata().issuer).to.equal(`${issuerBase}/oidc`) + }) + }) + + it('rejects HTTP issuer discovery when auth.insecureAllowHttp is false', async () => { + await withEmbeddedIssuerServer(async (issuerBase) => { + const oidc = reloadOidcModule() + oidc.initOidc() + + try { + await oidc.getOauthClientConfiguration() + expect.fail('expected HTTP discovery to be rejected') + } catch (error) { + expect(error.message).to.equal('only requests to HTTPS are allowed') + } + }) + }) + }) }) diff --git a/test/support/embedded-auth-harness.js b/test/support/embedded-auth-harness.js index 8168df91..b739234c 100644 --- a/test/support/embedded-auth-harness.js +++ b/test/support/embedded-auth-harness.js @@ -106,7 +106,8 @@ function createEmbeddedAuthStore () { groupNames = ['viewer'], mfaEnabled = false, totpSecret = null, - isBootstrap = false + isBootstrap = false, + mustChangePassword = false }) { const normalizedEmail = String(email).trim().toLowerCase() const userId = crypto.randomUUID() @@ -115,7 +116,7 @@ function createEmbeddedAuthStore () { id: userId, email: normalizedEmail, passwordHash, - mustChangePassword: false, + mustChangePassword, isBootstrap, failedAttempts: 0, lockedUntil: null, @@ -363,6 +364,8 @@ function reloadAuthModules ({ keepJwks = false } = {}) { function resetEmbeddedAuthCaches () { require('../../src/config/oidc').resetDiscoveryForTests() require('../../src/config/auth-jwks').resetSigningMaterialCacheForTests() + require('../../src/config/auth-session-store').resetAuthSessionStoreForTests() + require('../../src/services/auth-interaction-state-store').resetInteractionStateForTests() } async function createEmbeddedAuthHarness (sandbox, options = {}) { diff --git a/test/support/oidc-test-helpers.js b/test/support/oidc-test-helpers.js index 47cc3972..148e81d4 100644 --- a/test/support/oidc-test-helpers.js +++ b/test/support/oidc-test-helpers.js @@ -5,7 +5,11 @@ const OIDC_ENV_KEYS = [ 'OIDC_CLIENT_ID', 'OIDC_CLIENT_SECRET', 'OIDC_VIEWER_CLIENT_ID', - 'AUTH_VIEWER_CLIENT_ENABLED' + 'AUTH_VIEWER_CLIENT_ENABLED', + 'AUTH_SESSION_STORE_TYPE', + 'AUTH_SESSION_STORE_TTL_MS', + 'AUTH_SESSION_SECRET', + 'AUTH_INSECURE_ALLOW_HTTP' ] function snapshotOidcEnv () { From 329a0b6b28d5549f525cc76a6b42e82c7bfd06ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 17:39:00 +0300 Subject: [PATCH 54/75] Complete v3.8 RBAC catalog sync, system roles, drift audit, and operator documentation. Map all live routes, remove orphan entries, align built-in roles, and add npm run rbac-audit with operator reference docs. --- docs/rbac-reference.md | 190 ++++++++++++++++++++++++++++++++ package.json | 3 +- scripts/rbac-audit.js | 155 ++++++++++++++++++++++++++ scripts/route-inventory.js | 108 ++++++++++++++++++ src/config/rbac-resources.yaml | 10 +- src/config/rbac-system-roles.js | 8 +- 6 files changed, 462 insertions(+), 12 deletions(-) create mode 100644 docs/rbac-reference.md create mode 100644 scripts/rbac-audit.js create mode 100644 scripts/route-inventory.js diff --git a/docs/rbac-reference.md b/docs/rbac-reference.md new file mode 100644 index 00000000..e324ad13 --- /dev/null +++ b/docs/rbac-reference.md @@ -0,0 +1,190 @@ +# Controller RBAC — operator reference + +**Audience:** Platform operators, SREs, and integrators configuring access to Controller v3.8 +**Applies to:** User and admin HTTP APIs under `/api/v3/*` (not Edgelet agent wire protocol) + +## Overview + +Controller uses Kubernetes-style **Role** and **RoleBinding** objects. Each HTTP route is mapped in `src/config/rbac-resources.yaml` to a **resource** and **verb**. At request time the RBAC middleware: + +1. Validates the OIDC bearer JWT (or allows unauthenticated routes — see below). +2. Resolves **subjects** from token claims (User + Group). +3. Looks up RoleBindings for those subjects. +4. Evaluates Role rules against the route’s required resource and verb. + +Built-in **system roles** are defined in `src/config/rbac-system-roles.js`. Custom roles are stored in the database and managed via `/api/v3/roles` and `/api/v3/rolebindings`. + +## Authorization flow + +```text +Client request + → OIDC middleware (Bearer JWT, except agent routes and public endpoints) + → RBAC protect() middleware + → findRouteDefinition(method, path) in rbac-resources.yaml + → authorizer.authorize(subjects, apiGroup, resource, verb, resourceName) + → 200 / 403 +``` + +**Subjects** are derived from the access token (`src/lib/rbac/middleware.js`): + +| Subject kind | Source claim(s) | +|--------------|-----------------| +| **User** | `preferred_username`, `username`, `email`, or `sub` | +| **Group** | `resource_access[].roles`, `roles[]`, `groups[]` (lowercased) | + +Group names should match RoleBinding subjects and IdP role names (for example `admin`, `developer`, `viewer`). See [external-oidc-client-setup.md](external-oidc-client-setup.md) for external IdP claim mapping. + +### Password-change gate + +When the JWT carries `password_change_required`, only these routes are allowed until the user changes their password: + +- `GET /api/v3/user/profile` +- `POST /api/v3/user/change-password` + +All other RBAC-protected routes return **403**. + +## Route classes + +| Class | Auth | RBAC check | Examples | +|-------|------|------------|----------| +| **Public** | None | None | `GET /api/v3/status`, `GET /api/v3/architectures/` | +| **Auth-only** | Bearer JWT | Skipped (`verbs: []` in catalog) | `POST /api/v3/user/login`, OAuth BFF routes | +| **User RBAC** | Bearer JWT | Resource + verb from catalog | Most `/api/v3/*` admin APIs | +| **Agent wire** | Fog JWT | Separate from user RBAC | `/api/v3/agent/*` | + +Routes with an **empty verb list** (`[]`) are catalogued for inventory but do not trigger an RBAC rule lookup; they still require authentication when the route handler is behind OIDC middleware. + +**Agent routes** (`/api/v3/agent/*`) use fog provisioning tokens, not user OIDC RBAC. They are listed under the `agent` resource in `rbac-resources.yaml` for drift auditing only. The `agent-admin` system role applies to Edgelet service-account APIs (`edgelet.iofog.org/v1`), not to human users calling the Controller API. + +## System roles + +| Role | Scope | Typical use | +|------|-------|-------------| +| **admin** | `resources: ['*'], verbs: ['*']` | Full cluster administration; cannot be modified or deleted | +| **sre** | Full access to operational resources; read-only on `roles`, `roleBindings`, `natsOperator`, `natsBootstrap`, `natsHub` | Day-2 operations, NATS, cluster, exec/logs | +| **developer** | CRUD on workloads (microservices, applications, catalog, secrets, …); read-only on infra (fogs, router, cluster, NATS operator, system logs, …) | Application developers | +| **viewer** | `get`, `list` on read-only resource set | Read-only dashboards | +| **agent-admin** | `edgelet.iofog.org/v1` `*` | Edgelet service accounts (not human API users) | +| **microservice** | Limited self-service endpoints on Edgelet API group | Running microservice workloads | + +### Resource coverage by system role + +The table below lists **user API** resources in `rbac-resources.yaml` and which system roles include them. Resources marked *admin only* or *agent wire* are not granted to SRE/developer/viewer by default. + +| Resource | SRE | Developer | Viewer | Notes | +|----------|-----|-----------|--------|-------| +| microservices | ✓ | ✓ | ✓ | | +| systemMicroservices | ✓ | read | ✓ | | +| fogs | ✓ | read | ✓ | | +| applications | ✓ | ✓ | ✓ | Replaces legacy `flows` | +| systemApplications | ✓ | read | ✓ | | +| applicationTemplates | ✓ | ✓ | ✓ | | +| services | ✓ | ✓ | ✓ | | +| router | ✓ | read | ✓ | | +| cluster | ✓ | read | ✓ | v3.8 HA controllers | +| natsOperator, natsBootstrap, natsHub | read / ✓ | read | read | SRE has full NATS CRUD except operator objects (read) | +| natsAccounts, natsUsers, natsAccountRules, natsUserRules | ✓ | ✓ | ✓ | | +| catalog, registries | ✓ | ✓ | ✓ | | +| secrets, configMaps, volumeMounts | ✓ | ✓ | ✓ | | +| tunnels | ✓ | read | — | Viewer intentionally omits exec/tunnel paths | +| certificates, capabilities | ✓ | ✓ | ✓ | | +| execSessions, logs | ✓ | ✓ | — | | +| systemExecSessions, systemLogs | ✓ | read | — | | +| events | ✓ | — | — | SRE-only operational events | +| users (profile/MFA) | ✓ | — | — | User self-service routes use auth-only verbs | +| authUsers, authGroups | ✓ | read | read | Embedded identity admin | +| config, controller | ✓ | read | read | `controller` = status/architectures public routes | +| roles, roleBindings | read | read | read | Mutations require admin or custom roles | +| serviceAccounts | ✓ | ✓ | ✓ | | +| authAdmin | — | — | — | *Admin only* — JWKS rotate, auth migration | +| agent | — | — | — | *Agent wire* — fog token, not user RBAC | + +Bind system roles to users or groups with RoleBindings, for example: + +```yaml +apiVersion: datasance.com/v3 +kind: RoleBinding +metadata: + name: alice-developer +subjects: + - kind: User + name: alice@example.com +roleRef: + kind: Role + name: developer +``` + +## Verbs + +Standard verbs match Kubernetes conventions: + +| Verb | Typical HTTP methods | +|------|---------------------| +| `get` | GET single resource, HEAD | +| `list` | GET collection | +| `create` | POST | +| `update` | PUT | +| `patch` | PATCH, some POST sub-actions | +| `delete` | DELETE | + +Some sub-resource actions map to `patch` (for example microservice start/stop). WebSocket routes use verb `get` with method `WS`. + +## v3.8 RBAC changes + +| v3.7 / legacy | v3.8 | +|---------------|------| +| RBAC resource `flows` | **`applications`** / `systemApplications` | +| `GET /api/v3/fog-types` | **`GET /api/v3/architectures/`** (public) | +| EdgeResource, diagnostics, strace | **Removed** — no yaml entries | +| `POST /api/v3/agent/controller/register` | Added under **`agent`** resource (fog token) | +| OIDC auth routes (OAuth BFF, interactions) | Catalogued under **`users`** with auth-only verbs | + +Orphan RBAC entries for removed APIs must not reappear. CI and local checks enforce this (see Maintenance). + +## Custom roles + +Operators can define additional Roles via API or YAML: + +- `GET/POST /api/v3/roles`, `GET/PATCH/DELETE /api/v3/roles/:name` +- `GET/POST /api/v3/rolebindings`, `GET/PATCH/DELETE /api/v3/rolebindings/:name` + +Custom roles use `apiVersion: datasance.com/v3` and the same resource names as the route catalog. The **admin** system role always wins via `*` rules. + +## Maintenance and drift checks + +Keep `rbac-resources.yaml`, live routes, and system roles aligned when adding or removing APIs. + +```bash +nvm use 24 + +# Compare Express routes to rbac-resources.yaml (243 routes as of Plan 9) +npm run rbac-audit + +# Plan 9 grep gates — banned legacy terms must be absent; +# v3.8 terms must be present +rg 'edgeResources|diagnostics' src/config/rbac-resources.yaml && exit 1 || true +rg 'fog-types' src/config/rbac-resources.yaml && exit 1 || true +rg 'architectures|controller/register' src/config/rbac-resources.yaml +``` + +`npm run rbac-audit` exits non-zero on: + +- Live routes missing from the yaml catalog (gaps) +- Yaml entries with no matching route (orphans) +- Banned legacy terms (`edgeResources`, `diagnostics`, `fog-types`) +- Missing required v3.8 terms (`architectures`, `controller/register`) + +Optional CI wiring is planned for Plan 11. + +## Reference files + +| Topic | Path | +|-------|------| +| Route → resource catalog | `src/config/rbac-resources.yaml` | +| System roles | `src/config/rbac-system-roles.js` | +| RBAC middleware | `src/lib/rbac/middleware.js` | +| Authorizer | `src/lib/rbac/authorizer.js` | +| Route inventory (generated) | `.cursor/controllerv3.8/docs/09-rbac-audit-route-inventory.md` | +| Drift script | `scripts/rbac-audit.js`, `scripts/route-inventory.js` | +| External IdP groups | `docs/external-oidc-client-setup.md` | +| HTTP API spec | `docs/swagger.yaml` | diff --git a/package.json b/package.json index 6c9ff401..e6063fde 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,8 @@ "precoverage": "npm run lint", "coverage": "node scripts/run-test.js coverage", "prepare": "npm run lint", - "swagger": "./generate-swagger.sh" + "swagger": "./generate-swagger.sh", + "rbac-audit": "node scripts/rbac-audit.js" }, "preferGlobal": true, "bin": { diff --git a/scripts/rbac-audit.js b/scripts/rbac-audit.js new file mode 100644 index 00000000..f7d0bd6e --- /dev/null +++ b/scripts/rbac-audit.js @@ -0,0 +1,155 @@ +#!/usr/bin/env node +/* + * Compare live Express routes (src/routes/**) to rbac-resources.yaml. + * Exits non-zero on gaps (unmapped routes) or orphans (stale yaml entries). + * Plan 9 phase 9-5 — optional CI drift check. + */ + +const fs = require('fs') +const path = require('path') +const yaml = require('js-yaml') +const { collectAllRoutes } = require('./route-inventory') + +const RBAC_CATALOG_PATH = path.join(__dirname, '..', 'src', 'config', 'rbac-resources.yaml') + +const BANNED_YAML_TERMS = ['edgeResources', 'diagnostics', 'fog-types'] +const REQUIRED_YAML_TERMS = ['architectures', 'controller/register'] + +function routeKey (route) { + return `${route.method} ${route.path}` +} + +function collectYamlRoutes (catalogPath = RBAC_CATALOG_PATH) { + const catalog = yaml.load(fs.readFileSync(catalogPath, 'utf8')) + const routes = [] + + for (const [resource, def] of Object.entries(catalog.resources || {})) { + for (const route of def.routes || []) { + for (const method of Object.keys(route.methods || {})) { + routes.push({ + method: method.toUpperCase(), + path: route.path, + resource + }) + } + } + } + + routes.sort((a, b) => { + if (a.path !== b.path) { + return a.path.localeCompare(b.path) + } + return a.method.localeCompare(b.method) + }) + + return routes +} + +function compareRoutes (inventoryRoutes, yamlRoutes) { + const inventoryByKey = new Map(inventoryRoutes.map((route) => [routeKey(route), route])) + const yamlByKey = new Map(yamlRoutes.map((route) => [routeKey(route), route])) + + const gaps = inventoryRoutes + .filter((route) => !yamlByKey.has(routeKey(route))) + .map((route) => ({ + method: route.method, + path: route.path, + sourceFile: route.sourceFile + })) + + const orphans = yamlRoutes + .filter((route) => !inventoryByKey.has(routeKey(route))) + .map((route) => ({ + method: route.method, + path: route.path, + resource: route.resource + })) + + return { gaps, orphans } +} + +function checkYamlTerms (catalogPath = RBAC_CATALOG_PATH) { + const content = fs.readFileSync(catalogPath, 'utf8') + const banned = BANNED_YAML_TERMS.filter((term) => content.includes(term)) + const missing = REQUIRED_YAML_TERMS.filter((term) => !content.includes(term)) + + return { banned, missing } +} + +function auditRbac () { + const inventoryRoutes = collectAllRoutes() + const yamlRoutes = collectYamlRoutes() + const { gaps, orphans } = compareRoutes(inventoryRoutes, yamlRoutes) + const { banned, missing } = checkYamlTerms() + + return { + inventoryCount: inventoryRoutes.length, + yamlCount: yamlRoutes.length, + gaps, + orphans, + banned, + missing + } +} + +function printReport (result) { + const lines = [ + 'RBAC audit failed.', + '', + `Inventory routes: ${result.inventoryCount}`, + `YAML route-method entries: ${result.yamlCount}` + ] + + if (result.gaps.length) { + lines.push('', 'Gaps (live routes missing from rbac-resources.yaml):') + for (const gap of result.gaps) { + lines.push(` ${gap.method} ${gap.path} (${gap.sourceFile})`) + } + } + + if (result.orphans.length) { + lines.push('', 'Orphans (yaml entries with no live route):') + for (const orphan of result.orphans) { + lines.push(` ${orphan.method} ${orphan.path} (${orphan.resource})`) + } + } + + if (result.banned.length) { + lines.push('', 'Banned terms found in rbac-resources.yaml:', ...result.banned.map((term) => ` ${term}`)) + } + + if (result.missing.length) { + lines.push('', 'Required terms missing from rbac-resources.yaml:', ...result.missing.map((term) => ` ${term}`)) + } + + process.stderr.write(lines.join('\n') + '\n') +} + +function main () { + const result = auditRbac() + const failed = result.gaps.length > 0 || + result.orphans.length > 0 || + result.banned.length > 0 || + result.missing.length > 0 + + if (failed) { + printReport(result) + process.exit(1) + } + + process.stdout.write( + `RBAC audit passed: ${result.inventoryCount} routes matched, no drift.\n` + ) +} + +if (require.main === module) { + main() +} + +module.exports = { + auditRbac, + collectYamlRoutes, + compareRoutes, + checkYamlTerms, + routeKey +} diff --git a/scripts/route-inventory.js b/scripts/route-inventory.js new file mode 100644 index 00000000..a00f167b --- /dev/null +++ b/scripts/route-inventory.js @@ -0,0 +1,108 @@ +#!/usr/bin/env node +/* + * List all registered Express routes from src/routes/**. + */ + +const fs = require('fs') +const path = require('path') + +const ROUTES_DIR = path.join(__dirname, '..', 'src', 'routes') +const ROUTE_ENTRY_RE = /\{\s*method:\s*['"]([^'"]+)['"]\s*,\s*path:\s*['"]([^'"]+)['"]/g + +function collectRoutesFromFile (filePath) { + const source = fs.readFileSync(filePath, 'utf8') + const routes = [] + let match + + while ((match = ROUTE_ENTRY_RE.exec(source)) !== null) { + const blockStart = match.index + const blockEnd = source.indexOf('\n }', blockStart) + const block = blockEnd === -1 ? source.slice(blockStart) : source.slice(blockStart, blockEnd) + + routes.push({ + method: match[1].toUpperCase(), + path: match[2], + supportSubstitution: /supportSubstitution:\s*true/.test(block), + fileInput: (block.match(/fileInput:\s*['"]([^'"]+)['"]/) || [])[1] || null + }) + } + + return routes +} + +function collectAllRoutes () { + const files = fs.readdirSync(ROUTES_DIR) + .filter((name) => name.endsWith('.js')) + .sort() + + const routes = [] + + for (const file of files) { + const filePath = path.join(ROUTES_DIR, file) + for (const route of collectRoutesFromFile(filePath)) { + routes.push({ ...route, sourceFile: file }) + } + } + + routes.sort((a, b) => { + if (a.path !== b.path) { + return a.path.localeCompare(b.path) + } + return a.method.localeCompare(b.method) + }) + + return routes +} + +function toMarkdown (routes) { + const lines = [ + '# Route inventory', + '', + `Generated from \`src/routes/**\` on ${new Date().toISOString().slice(0, 10)}.`, + '', + `**Total routes:** ${routes.length}`, + '', + '| Method | Path | Source file | Notes |', + '|--------|------|-------------|-------|' + ] + + for (const route of routes) { + const notes = [] + if (route.method === 'WS') { + notes.push('WebSocket') + } + if (route.supportSubstitution) { + notes.push('template substitution') + } + if (route.fileInput) { + notes.push(`file upload: ${route.fileInput}`) + } + + lines.push(`| ${route.method} | \`${route.path}\` | \`${route.sourceFile}\` | ${notes.join('; ') || '—'} |`) + } + + lines.push('') + return lines.join('\n') +} + +function main () { + const format = process.argv.includes('--json') ? 'json' : 'markdown' + const routes = collectAllRoutes() + + if (format === 'json') { + process.stdout.write(JSON.stringify(routes, null, 2) + '\n') + return + } + + process.stdout.write(toMarkdown(routes)) +} + +if (require.main === module) { + main() +} + +module.exports = { + collectAllRoutes, + collectRoutesFromFile, + toMarkdown +} diff --git a/src/config/rbac-resources.yaml b/src/config/rbac-resources.yaml index 91f2f60b..bf46db85 100644 --- a/src/config/rbac-resources.yaml +++ b/src/config/rbac-resources.yaml @@ -21,7 +21,6 @@ resources: methods: GET: [get] PATCH: [patch] - PUT: [update] DELETE: [delete] resourceNameParam: uuid - path: /api/v3/microservices/:uuid/rebuild @@ -397,12 +396,6 @@ resources: PATCH: [patch] DELETE: [delete] resourceNameParam: id - - path: /api/v3/catalog/microservices/:id/images - methods: - GET: [get] - POST: [create] - DELETE: [delete] - resourceNameParam: id # Registries registries: @@ -634,6 +627,9 @@ resources: - path: /api/v3/agent/cert methods: GET: [get] + - path: /api/v3/agent/controller/register + methods: + POST: [create] - path: /api/v3/agent/logs/sessions methods: GET: [list] diff --git a/src/config/rbac-system-roles.js b/src/config/rbac-system-roles.js index 061a91ed..8861f7f7 100644 --- a/src/config/rbac-system-roles.js +++ b/src/config/rbac-system-roles.js @@ -48,12 +48,12 @@ module.exports = { rules: [ { apiGroups: [''], - resources: ['microservices', 'systemMicroservices', 'fogs', 'applications', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'tunnels', 'certificates', 'capabilities', 'serviceAccounts', 'events', 'users', 'authUsers', 'authGroups', 'config', 'controller', 'execSessions', 'systemExecSessions', 'logs', 'systemLogs'], + resources: ['microservices', 'systemMicroservices', 'fogs', 'applications', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'tunnels', 'certificates', 'capabilities', 'cluster', 'serviceAccounts', 'events', 'users', 'authUsers', 'authGroups', 'config', 'controller', 'execSessions', 'systemExecSessions', 'logs', 'systemLogs'], verbs: ['*'] }, { apiGroups: [''], - resources: ['roles', 'roleBindings', 'natsOperator', 'natsHub'], + resources: ['roles', 'roleBindings', 'natsOperator', 'natsBootstrap', 'natsHub'], verbs: ['get', 'list'] } ] @@ -73,7 +73,7 @@ module.exports = { }, { apiGroups: [''], - resources: ['fogs', 'router', 'tunnels', 'users', 'authUsers', 'authGroups', 'config', 'roles', 'roleBindings', 'systemMicroservices', 'systemApplications', 'natsOperator', 'natsHub'], + resources: ['fogs', 'router', 'tunnels', 'users', 'authUsers', 'authGroups', 'config', 'roles', 'roleBindings', 'systemMicroservices', 'systemApplications', 'systemExecSessions', 'systemLogs', 'cluster', 'natsOperator', 'natsBootstrap', 'natsHub'], verbs: ['get', 'list'] } ] @@ -88,7 +88,7 @@ module.exports = { rules: [ { apiGroups: [''], - resources: ['microservices', 'fogs', 'applications', 'systemMicroservices', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsOperator', 'natsHub', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'certificates', 'capabilities', 'serviceAccounts', 'config', 'controller', 'roles', 'roleBindings'], + resources: ['microservices', 'fogs', 'applications', 'systemMicroservices', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsOperator', 'natsBootstrap', 'natsHub', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'certificates', 'capabilities', 'cluster', 'serviceAccounts', 'users', 'authUsers', 'authGroups', 'config', 'controller', 'roles', 'roleBindings'], verbs: ['get', 'list'] } ] From 28744d28483c9c629a11aa4a8da2102d9d88a029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 23:25:47 +0300 Subject: [PATCH 55/75] Remove Postman/Newman integration from the test runner. Drop the collection, runner script, and npm scripts so CI relies on mocha and cli-tests only. --- scripts/postmantest.js | 55 - scripts/run-test.js | 4 - test/postman_collection.json | 9406 ---------------------------------- 3 files changed, 9465 deletions(-) delete mode 100644 scripts/postmantest.js delete mode 100644 test/postman_collection.json diff --git a/scripts/postmantest.js b/scripts/postmantest.js deleted file mode 100644 index 555254a3..00000000 --- a/scripts/postmantest.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const newman = require('newman') -const { init } = require('./init') -const { restoreDBs, backupDBs } = require('./util') -const { start } = require('./start') -const { stop } = require('./stop') - -function postmanTest () { - stop() - backupDBs() - // create new DBs - init() - start() - // call newman.run to pass `options` object and wait for callback - newman.run({ - collection: require('../test/postman_collection.json'), - reporters: ['cli', 'junit'], - // reporter: { junitfull: { export: './integration-results.xml' } } - reporter: { junit: { export: './integration-results.xml' } } - // abortOnError: true, - // abortOnFailure: true - }).on('start', function (err, args) { // on start of run, log to console - if (err) { - console.log('Error: ', err) - } - console.log('running a collection...') - }).on('done', function (err, summary) { - if (err || summary.error || summary.run.failures.length !== 0) { - restoreDBs() - stop() - console.error('collection run encountered an error. tests did not pass.') - process.exitCode = 1 - } else { - restoreDBs() - stop() - console.log('collection run completed.') - } - }) -} - -module.exports = { - postmanTest -} diff --git a/scripts/run-test.js b/scripts/run-test.js index e6eb8230..817a3a42 100644 --- a/scripts/run-test.js +++ b/scripts/run-test.js @@ -14,7 +14,6 @@ const { test } = require('./test') const { cliTest } = require('./cli-tests') const { coverage } = require('./coverage') -const { postmanTest } = require('./postmantest') switch (process.argv[2]) { case 'test': { @@ -30,9 +29,6 @@ switch (process.argv[2]) { case 'coverage': coverage() break - case 'postmantest': - postmanTest() - break default: console.log('no script for this command') break diff --git a/test/postman_collection.json b/test/postman_collection.json deleted file mode 100644 index 0419d9fa..00000000 --- a/test/postman_collection.json +++ /dev/null @@ -1,9406 +0,0 @@ -{ - "info": { - "_postman_id": "df6a958a-20f9-4d84-b707-b8af99164b77", - "name": "Controller Testing", - "description": "iofog-controller collection", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "User", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"xxxx-xxxx-xxxx-xxxx\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Activate user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Error message is valid\"] = data.name === \"NotFoundError\" && data.message === \"Activation code not found\";" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"activationCode\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/activate", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "activate" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "Logout", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/logout", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "logout" - ] - } - }, - "response": [] - }, - { - "name": "Login 2", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "Resend activation email", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{host}}/api/v3/user/signup/resend-activation?email=user@domain.com", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup", - "resend-activation" - ], - "query": [ - { - "key": "email", - "value": "user@domain.com" - } - ] - } - }, - "response": [] - }, - { - "name": "Get user profile", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.firstName && data.lastName && data.email && data.subscriptionKey;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - }, - { - "name": "Reset password", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/password", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "password" - ] - } - }, - "response": [] - }, - { - "name": "Update user profile", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.firstName && data.lastName && data.email && data.subscriptionKey;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"Saeid\",\n \"lastName\": \"Rezaei\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - }, - { - "name": "Change password", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"oldPassword\": \"#Bugs4Fun\",\n \"newPassword\": \"#Bugs4Fun2\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/password", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "password" - ] - } - }, - "response": [] - }, - { - "name": "Login with new password", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun2\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "User collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "General", - "item": [ - { - "name": "Status", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.status && data.hasOwnProperty('timestamp');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{host}}/api/v3/status", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "status" - ] - } - }, - "response": [] - }, - { - "name": "Get email activation setting", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('isEmailActivationEnabled');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{host}}/api/v3/email-activation", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "email-activation" - ] - } - }, - "response": [] - }, - { - "name": "Get Fog types", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.fogTypes;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{host}}/api/v3/fog-types", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "fog-types" - ] - } - }, - "response": [] - } - ], - "description": "General collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Agent", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "Provisioning Key", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.key;", - "", - "postman.setGlobalVariable(\"provisioning-key\", data.key);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/provisioning-key", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "provisioning-key" - ] - } - }, - "response": [] - }, - { - "name": "Agent provision", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.token;", - "", - "postman.setGlobalVariable(\"agent-token\", data.token);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"{{provisioning-key}}\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/provision", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "provision" - ] - } - }, - "response": [] - }, - { - "name": "Get agent config", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.networkInterface && data.dockerUrl && data.hasOwnProperty('diskLimit') && data.diskDirectory", - "&& data.hasOwnProperty('memoryLimit') && data.hasOwnProperty('cpuLimit') && data.hasOwnProperty('logLimit') ", - "&& data.logDirectory && data.hasOwnProperty('logFileCount') ", - "&& data.hasOwnProperty('statusFrequency') && data.hasOwnProperty('changeFrequency') && data.hasOwnProperty('deviceScanFrequency') && data.hasOwnProperty('watchdogEnabled')", - "&& data.hasOwnProperty('latitude') && data.hasOwnProperty('longitude')", - "&& data.hasOwnProperty('routerHost') && data.hasOwnProperty('routerPort')" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/config", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "config" - ] - } - }, - "response": [] - }, - { - "name": "Get image snapshot", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === \"NotFoundError\" && data.message === \"Image snapshot not found\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/image-snapshot", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "image-snapshot" - ] - } - }, - "response": [] - }, - { - "name": "Get agent microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === \"NotFoundError\" && data.message === \"Invalid microservice UUID 'abcedf'\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/microservices/abcedf", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "microservices", - "abcedf" - ] - } - }, - "response": [] - }, - { - "name": "Update hardware info", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"info\": \"testData\"\r\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/hal/hw", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "hal", - "hw" - ] - } - }, - "response": [] - }, - { - "name": "Update USB info", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"info\": \"testData2\"\r\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/hal/usb", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "hal", - "usb" - ] - } - }, - "response": [] - }, - { - "name": "Update agent status", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"daemonStatus\": \"RUNNING\",\r\n \"daemonOperatingDuration\": 15,\r\n \"daemonLastStart\": 25,\r\n \"memoryUsage\": 16,\r\n \"diskUsage\": 14,\r\n \"cpuUsage\": 17,\r\n \"memoryViolation\": true,\r\n \"diskViolation\": true,\r\n \"cpuViolation\": true,\r\n \"microserviceStatus\": \"[]\",\r\n \"repositoryCount\": 5,\r\n \"repositoryStatus\": \"RUNNING\",\r\n \"systemTime\": 155,\r\n \"lastStatusTime\": 166,\r\n \"ipAddress\": \"192.168.0.1\",\r\n \"processedMessages\": 255,\r\n \"microserviceMessageCounts\": \"counts\",\r\n \"messageSpeed\": 52,\r\n \"lastCommandTime\": 57,\r\n \"tunnelStatus\": \"on\",\r\n \"version\": \"1\",\r\n \"isReadyToUpgrade\": true,\r\n \"isReadyToRollback\": true\r\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/status", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "status" - ] - } - }, - "response": [] - }, - { - "name": "Update image snapshot", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 400\"] = responseCode.code === 400;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === \"ValidationError\" && data.message === \"Invalid content type\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/zip" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"info\": \"testData2\"\r\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/image-snapshot", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "image-snapshot" - ] - } - }, - "response": [] - }, - { - "name": "New Application with microservices and ports", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-msvc-name\", data.name);", - "postman.setGlobalVariable(\"application-route-name\", \"m1-2\");", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-msvc-name\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true,\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [{\n \"internal\": 81,\n \"external\": 5001\n }, {\n \"internal\": 80,\n \"external\": 5002,\n \"protocol\": \"udp\"\n }],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-3\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ],\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-2\",\n \"name\": \"m1-2\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Update agent config", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"networkInterface\": \"wlan0\",\r\n \"dockerUrl\": \"http://google.com\",\r\n \"diskLimit\": 15,\r\n \"diskDirectory\": \"testDirectoryPath\",\r\n \"memoryLimit\": 150,\r\n \"cpuLimit\": 17,\r\n \"logLimit\": 16,\r\n \"logDirectory\": \"testLogPath\",\r\n \"logFileCount\": 7,\r\n \"statusFrequency\": 35,\r\n \"changeFrequency\": 36,\r\n \"deviceScanFrequency\": 37,\r\n \"watchdogEnabled\": true,\r\n \"latitude\": 22,\r\n \"longitude\": 66,\r\n \"gpsMode\": \"manual\",\r\n \"dockerPruningFrequency\": 35,\r\n \"availableDiskThreshold\": 95,\r\n \"logLevel\": \"INFO\"\r\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/config", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "config" - ] - } - }, - "response": [] - }, - { - "name": "Get agent config changes", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('config') && data.hasOwnProperty('version') && data.hasOwnProperty('reboot')", - "&& data.hasOwnProperty('deleteNode') && data.hasOwnProperty('microserviceList') && data.hasOwnProperty('microserviceConfig')", - "&& data.hasOwnProperty('registries') && data.hasOwnProperty('tunnel') && data.hasOwnProperty('prune');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/config/changes", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "config", - "changes" - ] - } - }, - "response": [] - }, - { - "name": "Get agent tunnel", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === \"NotFoundError\" && data.message === \"Tunnel not found\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/tunnel", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "tunnel" - ] - } - }, - "response": [] - }, - { - "name": "Get agent registries", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.registries;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/registries", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "registries" - ] - } - }, - "response": [] - }, - { - "name": "Get change version command", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === \"NotFoundError\" && data.message === \"Version command not found\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/version", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "version" - ] - } - }, - "response": [] - }, - { - "name": "Get agent microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.microservices.length;", - "", - "const msvc1 = data.microservices.find(m => m.portMappings && m.portMappings.length === 2)", - "", - "const udpPort = msvc1.portMappings.find(p => p.isUdp)", - "const tcpPort = msvc1.portMappings.find(p => !p.isUdp)", - "console.log({msvc1, udpPort, tcpPort})", - "", - "tests[\"Has UDP and TCP ports\"] = !!udpPort && !!tcpPort;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Agent collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Application", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-name\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "New Application with microservices and routes", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-msvc-name\", data.name);", - "postman.setGlobalVariable(\"application-route-name\", \"m1-2\");", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-msvc-name\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true,\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-3\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ],\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-2\",\n \"name\": \"m1-2\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "New Application using YAML file", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-yaml-name\", data.name);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "multipart/form-data" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "application", - "type": "file", - "src": "./test/application-test.yaml" - } - ] - }, - "url": { - "raw": "{{host}}/api/v3/application/yaml", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "yaml" - ] - } - }, - "response": [] - }, - { - "name": "Get Application route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.name && data.application === pm.globals.get(\"application-msvc-name\");", - "tests[\"Route is from msvc1 to msvc2\"] = data.from === \"msvc-1\" && data.to === \"msvc-2\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/routes/{{application-msvc-name}}/{{application-route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{application-msvc-name}}", - "{{application-route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-3\",\n \"name\": \"m1-2\"\n },\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-1\",\n \"name\": \"m1-1\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-msvc-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Application Routes", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.hasOwnProperty('isActivated');", - "", - "tests[\"Routes are updated\"] = data.hasOwnProperty('routes') && data.routes.length === 2;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/application/{{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-msvc-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Application route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.name && data.application === pm.globals.get(\"application-msvc-name\");", - "tests[\"Route is from msvc1 to msvc3\"] = data.from === \"msvc-1\" && data.to === \"msvc-3\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/routes/{{application-msvc-name}}/{{application-route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{application-msvc-name}}", - "{{application-route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservices From application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains two microservices\"] = data.microservices.length === 3;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", - "", - "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", - "", - "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices?application={{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ], - "query": [ - { - "key": "application", - "value": "{{application-msvc-name}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Update Application microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"description\": \"Description\",\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-msvc-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application using YAML", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "multipart/form-data" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "application", - "type": "file", - "src": "./test/application-update-test.yaml" - } - ] - }, - "url": { - "raw": "{{host}}/api/v3/application/yaml/{{application-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "yaml", - "{{application-yaml-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservices From YAML application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains two microservices\"] = data.microservices.length === 2;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", - "", - "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", - "", - "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');", - "", - "const viewer = data.microservices.find(m => m.name === 'heart-rate-viewer')", - "const wssPort = viewer.ports.find(p => p.external === 5002)", - "const defaultPort = viewer.ports.find(p => p.external === 5001)", - "const defaultPort2 = viewer.ports.find(p => p.external === 5003)", - "tests[\"Viewer has public port public links\"] = wssPort.public.links.length === 2 && defaultPort.public.links.length === 1", - "tests[\"Viewer wss public port uses defined port\"] = wssPort.public.links[0].endsWith(':5005')", - "tests[\"Viewer default public port uses default port\"] = defaultPort.public.links[0].endsWith(':6000')", - "tests[\"Viewer default2 public port uses default port\"] = defaultPort2.public.links[0].endsWith(':6001')" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices?application={{application-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ], - "query": [ - { - "key": "application", - "value": "{{application-yaml-name}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Microservices From application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains two microservices\"] = data.microservices.length === 2;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", - "", - "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", - "", - "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices?application={{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ], - "query": [ - { - "key": "application", - "value": "{{application-msvc-name}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Applications", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applications') && data.applications.length > 0;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Get Application By Name", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.hasOwnProperty('isActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "postman.setGlobalVariable(\"application-name\", \"application-name-22\");" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name-22\",\n \"isSystem\": true,\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Applications without system", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applications') && data.applications.length === 2;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application With microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-msvc-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application YAML", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-yaml-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Applications", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applications') && data.applications.length === 0;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Get All Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains three microservices\"] = data.microservices.length === 0;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Application route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/route/{{application-route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "route", - "{{application-route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Application collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Application Template", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n }" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Application Template", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "if (tests[\"Response validation passed\"]) {", - " postman.setGlobalVariable(\"application-template-name\", data.name);", - " postman.setGlobalVariable(\"application-template-id\", data.id);", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-template-name\",\n \"variables\": [\n {\n \"key\": \"agent-1-name\",\n \"description\": \"Agent name for msvc-1\"\n },\n {\n \"key\": \"env-value-1\",\n \"description\": \"ENV variable value for KEY1\"\n },\n {\n \"key\": \"env-value-2\",\n \"description\": \"ENV variable value for KEY2\",\n \"defaultValue\": \"test42\"\n }\n ],\n \"application\": {\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"agentName\": \"{{ agent-1-name }}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"{{ env-value-1 }}\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"{{ env-value-2 }}\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-3\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ],\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-2\",\n \"name\": \"m1-2\"\n }\n ]\n }\n}" - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate" - ] - } - }, - "response": [] - }, - { - "name": "New Application Template From YAML", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "if (tests[\"Response validation passed\"]) {", - " postman.setGlobalVariable(\"application-template-from-yaml-name\", data.name);", - " postman.setGlobalVariable(\"application-template-from-yaml-id\", data.id);", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "template", - "type": "file", - "src": "./test/application-template.yaml" - } - ] - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/yaml", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "yaml" - ] - } - }, - "response": [] - }, - { - "name": "Update Application Template Microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"variables\": [\n {\n \"key\": \"agent-1-name\",\n \"description\": \"Agent name for msvc-1\"\n },\n {\n \"key\": \"env-value-1\",\n \"description\": \"ENV variable value for KEY1\"\n },\n {\n \"key\": \"env-value-2\",\n \"description\": \"ENV variable value for KEY2\",\n \"defaultValue\": \"test42\"\n }\n ],\n \"application\": {\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"agentName\": \"{{ agent-1-name }}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"{{ env-value-1 }}\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"{{ env-value-2 }}\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ],\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-2\",\n \"name\": \"m1-2\"\n }\n ]\n }\n}" - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/{{application-template-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "{{application-template-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application Template Microservices using YAML", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "template", - "type": "file", - "src": "./test/application-template-update.yaml" - } - ] - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/yaml/{{application-template-from-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "yaml", - "{{application-template-from-yaml-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application Template metadata", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "if (tests[\"Status code is 204\"]) {", - " postman.setGlobalVariable(\"application-template-name\", \"application-template-22\");", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-template-22\",\n \"description\": \"Description\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/{{application-template-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "{{application-template-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application Template metadata from YAML", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "if (tests[\"Status code is 204\"]) {", - " postman.setGlobalVariable(\"application-template-from-yaml-name\", \"application-template-from-yaml-22\");", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-template-from-yaml-22\",\n \"description\": \"Description\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/{{application-template-from-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "{{application-template-from-yaml-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Application Templates", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applicationTemplates') && data.applicationTemplates.length === 2;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/applicationTemplates", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplates" - ] - } - }, - "response": [] - }, - { - "name": "Get Application Template By Name", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.hasOwnProperty('application');", - "if (tests[\"Response validation passed\"]) {", - " const applicationData = data.application", - " tests[\"Application template is parsed properly\"] = applicationData.hasOwnProperty('microservices') && applicationData.microservices.length === 2 && applicationData.hasOwnProperty('routes') && applicationData.routes.length === 1;", - "", - " tests[\"Application template has variables\"] = data.hasOwnProperty('variables') && data.variables.length === 3;", - " ", - " const templatedMsvc = applicationData.microservices.find(m => m.name === \"msvc-1\")", - " tests[\"Application variables are OK\"] = templatedMsvc.agentName = \"{{ agent-1-name }}\"", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/{{application-template-from-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "{{application-template-from-yaml-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Deploy Application based on template", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "if (tests[\"Response validation passed\"]) {", - " postman.setGlobalVariable(\"application-name\", data.name);", - " postman.setGlobalVariable(\"application-id\", data.id);", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name-2\",\n \"template\": {\n \"name\": \"{{application-template-from-yaml-name}}\",\n \"variables\": [\n {\n \"key\": \"agent-1-name\",\n \"value\": \"string\"\n },\n {\n \"key\": \"env-value-1\",\n \"value\": \"12345\"\n },\n {\n \"key\": \"agent-2-name\",\n \"value\": \"string\"\n }\n ]\n },\n \"isSystem\": false,\n \"isActivated\": true,\n \"description\": \"new application from template\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Get Application By Name", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.hasOwnProperty('isActivated');", - "", - "if (tests[\"Response validation passed\"]) {", - " const msvc = data.microservices.find(m => m.name === \"msvc-1\");", - " tests[\"Agent uuid was converted properly\"] = msvc.iofogUuid === pm.globals.get(\"node-id\");", - " postman.setGlobalVariable(\"application-msvc-uuid\", msvc.uuid);", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Msvc By Name", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('uuid') && data.name;", - "", - "if (tests[\"Response validation passed\"]) {", - " const env3 = data.env.find(e => e.key === \"KEY1\")", - " const env2 = data.env.find(e => e.key === \"KEY2\")", - " tests[\"Env variable were parsed properly\"] = env3.value === \"12345\" && env2.value === \"test42\";", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{application-msvc-uuid}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{application-msvc-uuid}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application Template", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/{{application-template-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "{{application-template-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application Template from YAML", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/{{application-template-from-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "{{application-template-from-yaml-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Application collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Application with template", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node 1", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id-1\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"node1\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node 2", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id-2\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"node2\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1,\n \"host\": \"1.2.3.5\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-name-1\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name-1\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true,\n \"microservices\": [\n {\n \"name\": \"redistest\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"redis\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"redis\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-name-1\",\n \"iofogUuid\": \"{{node-id-1}}\",\n \"rootHostAccess\": false,\n \"logSize\": 0,\n \"volumeMappings\": [],\n \"ports\": [\n {\n \"internal\": 6379,\n \"external\": 6379,\n \"publicMode\": false\n }\n ]\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "New Application with microservices and routes", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-msvc-name\", data.name);", - "postman.setGlobalVariable(\"application-name-2\", data.name);", - "postman.setGlobalVariable(\"application-route-name\", \"m1-2\");", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-msvc-name\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true,\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-msvc-name\",\n \"iofogUuid\": \"{{node-id-2}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 1881,\n \"external\": 1882\n }\n ],\n \"routes\": [],\n \"env\": [\n { \n \"key\": \"selfname\",\n \"value\": \"{{ self.name | upcase }}\"\n },\n {\n \"key\": \"sharedToken\",\n \"value\": \"sekrittoken\"\n },\n {\n \"key\": \"http_proxy\",\n \"value\": \"http://myproxy:8080/\"\n },\n {\n \"key\": \"https_proxy\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\" , \\\"http_proxy\\\" | first | map: \\\"value\\\" | first }}\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-msvc-name\",\n \"iofogUuid\": \"{{node-id-2}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n { \n \"key\": \"selfname\",\n \"value\": \"{{ self.name | upcase }}\"\n },\n {\n \"key\": \"sharedToken\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\", \\\"sharedToken\\\" | first | map: \\\"value\\\" | first }}\"\n },\n {\n \"key\": \"http_proxy\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\" , \\\"http_proxy\\\" | first | map: \\\"value\\\" | first }}\"\n },\n {\n \"key\": \"https_proxy\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\" , \\\"http_proxy\\\" | first | map: \\\"value\\\" | first }}\"\n },\n {\n \"key\": \"rulesengineHOST\",\n \"value\": \"{% assign curmsvc= self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first %}{{ curmsvc | findMicroserviceAgent | map: \\\"host\\\" | first }}\"\n },\n {\n \"key\": \"rulesenginePORT\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"ports\\\" | first | map: \\\"external\\\" | first | toString }}\"\n },\n {\n \"key\": \"redisHost\",\n \"value\": \"{% assign redisApp = \\\"application-name-1\\\" | findApplication %}{% assign redismsvc = redisApp.microservices | where: \\\"name\\\", \\\"redistest\\\" | first %}{{ redismsvc | findMicroserviceAgent | map: \\\"host\\\"}}:{{ redismsvc | map: \\\"ports\\\" | first | first | map: \\\"external\\\" | first | toString }}\"\n }}\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-3\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-msvc-name\",\n \"iofogUuid\": \"{{node-id-2}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ],\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-2\",\n \"name\": \"m1-2\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservices From application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains two microservices\"] = data.microservices.length === 3;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", - "", - "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", - "", - "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');", - "", - "tests[\"msv3 gets env application name\"]=data.microservices[0].env[0].value === pm.globals.get(\"application-msvc-name\").toUpperCase()", - "tests[\"msv3 sets env value from another\"]=data.microservices[0].env[3].value === data.microservices[0].env[2].value", - "", - "tests[\"msv2 gets env application name\"]=data.microservices[1].env[0].value === pm.globals.get(\"application-msvc-name\").toUpperCase()", - "tests[\"msv2 sets env value from env service msvc1\"]=data.microservices[1].env[1].value === data.microservices[0].env[1].value", - "tests[\"msv2 sets env value from service msvc1\"]=data.microservices[1].env[5].value === data.microservices[0].ports[0].external.toString()", - "", - "tests[\"msv2 gets hostname from iofog agent for itself\"]=data.microservices[1].env[4].value === '1.2.3.5'", - "tests[\"msv2 gets hostname from iofog agent for a service of another app\"]=data.microservices[1].env[6].value === '1.2.3.4:6379'", - "", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices?application={{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ], - "query": [ - { - "key": "application", - "value": "{{application-msvc-name}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Applications", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applications') && data.applications.length > 0;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Get Application By Name", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.hasOwnProperty('isActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application/{{application-name-2}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name-2}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "postman.setGlobalVariable(\"application-name-1\", \"application-name-22\");" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name-22\",\n \"isSystem\": true,\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name-1}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name-1}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Applications without system", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applications') && data.applications.length === 1;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name-1}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name-1}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application With microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-msvc-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Applications", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applications') && data.applications.length === 0;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Get All Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains three microservices\"] = data.microservices.length === 0;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Application route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/route/{{application-route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "route", - "{{application-route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node 2", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id-2}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id-2}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node 1", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id-1}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id-1}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Application collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Catalog", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New Catalog Item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"item-id\", data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Catalog Items", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('catalogItems');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/catalog/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Catalog Item By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.category && data.hasOwnProperty('configExample')", - "&& data.publisher && data.hasOwnProperty('diskRequired') && data.hasOwnProperty('ramRequired') && data.picture && data.hasOwnProperty('isPublic')", - "&& data.hasOwnProperty('registryId') && data.hasOwnProperty('images') && data.hasOwnProperty('inputType') && data.hasOwnProperty('outputType');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Catalog Item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Catalog Item By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Catalog collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Tunnel", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1,\n \"dockerPruningFrequency\": 35,\n \"availableDiskThreshold\": 95,\n \"logLevel\": \"INFO\"\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "Open SSH Tunnel To ioFog Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"action\": \"open\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/tunnel", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "tunnel" - ] - } - }, - "response": [] - }, - { - "name": "Get Node SSH Tunnel Info", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/tunnel", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "tunnel" - ] - } - }, - "response": [] - }, - { - "name": "Close SSH Tunnel To ioFog Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"action\": \"close\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/tunnel", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "tunnel" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Tunnel collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Microservices", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-name\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Second Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-name-2\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name-second\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "New Catalog Item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"item-id\", data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"ms-name\", data.name);", - "postman.setGlobalVariable(\"ms-id\", data.uuid);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name2\",\n \"config\": \"string\",\n \"catalogItemId\": {{item-id}},\n \"application\": \"{{application-name}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 1,\n \"external\": 1,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice without catalog in second application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"namesec\",\n \"config\": \"string\",\n \"images\": [{\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice without catalog", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"ms-no-catalog-name\", data.name);", - "postman.setGlobalVariable(\"ms-no-catalog-id\", data.uuid);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name-without-catalog\",\n \"config\": \"string\",\n \"images\": [{\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 1,\n \"application\": \"{{application-name}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 2,\n \"external\": 2,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.name;", - "", - "postman.setGlobalVariable(\"route-name\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"route-name\",\n \"from\": \"{{ms-no-catalog-name}}\",\n \"to\": \"{{ms-name}}\",\n \"application\": \"{{application-name}}\"\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/routes", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes" - ] - } - }, - "response": [] - }, - { - "name": "Update Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "", - "postman.setGlobalVariable(\"route-name\", \"route-name-updated\");" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"route-name-updated\",\n \"from\": \"{{ms-no-catalog-name}}\",\n \"to\": \"{{ms-name}}\",\n \"application\": \"{{application-name}}\"\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/routes/{{application-name}}/{{route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{application-name}}", - "{{route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/routes/{{application-name}}/{{route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{application-name}}", - "{{route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get All Microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains three microservices\"] = data.microservices.length === 3;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains two microservices\"] = data.microservices.length === 2;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", - "", - "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", - "", - "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices?application={{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ], - "query": [ - { - "key": "application", - "value": "{{application-name}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Microservice By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservice without catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice without catalog item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"images\": [{\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 2,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "", - "", - "postman.setGlobalVariable(\"ms-name\", \"name3\");" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name3\",\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Microservice without catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world-updated\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.registryId === 2 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice without catalog item to give it a catalog item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"catalogItemId\": 14,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Microservice with catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 0 && data.hasOwnProperty('catalogItemId') && data.catalogItemId === 14 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice without catalog item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"images\": [{\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 2,\n \"catalogItemId\": null,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Microservice again without catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world-updated\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.registryId === 2 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Create a Route From Microservice to Receiver", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.name", - "", - "postman.setGlobalVariable(\"route-id\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\"from\": \"{{ms-name}}\",\n \"to\": \"{{ms-name}}\",\n \"name\": \"my-route\",\n \"application\": \"{{application-name}}\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/routes", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/routes/{{application-name}}/{{route-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{application-name}}", - "{{route-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Add a Port Mapping to Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"internal\": 15,\n \"external\": 155,\n \"publicMode\": false\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Get Port Mappings", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('ports');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Port Mapping By Provided Internal Port", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping/15", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping", - "15" - ] - } - }, - "response": [] - }, - { - "name": "Create volume mapping", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "", - "postman.setGlobalVariable(\"volume-id\", data.id);", - "" - ] - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": " {\n \"hostDestination\": \"/var/dest7\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping" - ] - } - }, - "response": [] - }, - { - "name": "List volume mappings", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('volumeMappings');" - ] - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Delete volume mapping", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ] - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping/{{volume-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping", - "{{volume-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Microservice", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ] - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"withCleanup\": false\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Catalog Item By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete User", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Microservices collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "ioFog", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "Provisioning Key", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.key;", - "", - "postman.setGlobalVariable(\"provisioning-key\", data.key);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/provisioning-key", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "provisioning-key" - ] - } - }, - "response": [] - }, - { - "name": "List ioFog Nodes", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('fogs') && data.fogs.length === 1;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog-list?filters[0][key]=uuid&filters[0][value]={{node-id}}&filters[0][condition]=equals", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog-list" - ], - "query": [ - { - "key": "filters[0][key]", - "value": "uuid" - }, - { - "key": "filters[0][value]", - "value": "{{node-id}}" - }, - { - "key": "filters[0][condition]", - "value": "equals" - } - ] - } - }, - "response": [] - }, - { - "name": "Update Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"isSystem\": false,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "List system fogs", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('fogs') && data.fogs.length === 1;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog-list?system=true", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog-list" - ], - "query": [ - { - "key": "system", - "value": "true" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Node By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.name && data.location && data.hasOwnProperty('gpsMode') && data.hasOwnProperty('latitude')", - "&& data.hasOwnProperty('longitude') && data.description && data.hasOwnProperty('lastActive') && data.daemonStatus && data.hasOwnProperty('daemonOperatingDuration') ", - "&& data.hasOwnProperty('daemonLastStart') && data.hasOwnProperty('memoryUsage') && data.hasOwnProperty('diskUsage') && data.hasOwnProperty('cpuUsage') ", - "&& data.hasOwnProperty('memoryViolation') && data.hasOwnProperty('diskViolation') && data.hasOwnProperty('cpuViolation') && data.hasOwnProperty('catalogItemStatus')", - "&& data.hasOwnProperty('repositoryCount') && data.hasOwnProperty('repositoryStatus') && data.hasOwnProperty('systemTime') && data.hasOwnProperty('lastStatusTime')", - "&& data.hasOwnProperty('ipAddress') && data.hasOwnProperty('processedMessages') && data.hasOwnProperty('catalogItemMessageCounts') && data.hasOwnProperty('messageSpeed')", - "&& data.hasOwnProperty('lastCommandTime') && data.hasOwnProperty('networkInterface') && data.hasOwnProperty('dockerUrl') && data.hasOwnProperty('diskLimit')", - "&& data.hasOwnProperty('diskDirectory') && data.hasOwnProperty('memoryLimit') && data.hasOwnProperty('cpuLimit') && data.hasOwnProperty('logLimit')", - "&& data.logDirectory && data.hasOwnProperty('bluetoothEnabled') && data.hasOwnProperty('abstractedHardwareEnabled') && data.hasOwnProperty('logFileCount') ", - "&& data.hasOwnProperty('version') && data.hasOwnProperty('isReadyToUpgrade') && data.hasOwnProperty('isReadyToRollback') && data.hasOwnProperty('statusFrequency')", - "&& data.hasOwnProperty('changeFrequency') && data.hasOwnProperty('deviceScanFrequency') && data.hasOwnProperty('tunnel') && data.hasOwnProperty('watchdogEnabled')", - "&& data.hasOwnProperty('fogTypeId') && data.hasOwnProperty('userId')", - "&& data.hasOwnProperty('logLevel') && data.hasOwnProperty('dockerPruningFrequency')", - "&& data.hasOwnProperty('availableDiskThreshold')", - "&& data.hasOwnProperty('timeZone')", - "&& data.hasOwnProperty('fogTypeId') && data.hasOwnProperty('userId') && data.hasOwnProperty('isSystem');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Node Version Command rollback", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 400;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === 'ValidationError' && data.message === 'Can\\'t rollback version now. There are no backups on agent';" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/version/rollback", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "version", - "rollback" - ] - }, - "description": "change version command\nAvailable values : upgrade, rollback" - }, - "response": [] - }, - { - "name": "Node Version Command upgrade", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/version/upgrade", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "version", - "upgrade" - ] - }, - "description": "change version command\nAvailable values : upgrade, rollback" - }, - "response": [] - }, - { - "name": "Reboot Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/reboot", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "reboot" - ] - } - }, - "response": [] - }, - { - "name": "Prune Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/prune", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "prune" - ] - } - }, - "response": [] - }, - { - "name": "Retrieves HAL Hardware Info", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/hal/hw", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "hal", - "hw" - ] - } - }, - "response": [] - }, - { - "name": "Retrieves HAL USB Info", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/hal/usb", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "hal", - "usb" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Check Node Deleted", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response does not contain any node\"] = data.hasOwnProperty('fogs') && data.fogs.length === 0;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog-list?filters[0][key]=uuid&filters[0][value]={{node-id}}&filters[0][condition]=equals", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog-list" - ], - "query": [ - { - "key": "filters[0][key]", - "value": "uuid" - }, - { - "key": "filters[0][value]", - "value": "{{node-id}}" - }, - { - "key": "filters[0][condition]", - "value": "equals" - } - ] - } - }, - "response": [] - }, - { - "name": "Delete User", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "ioFog collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Registries", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "Create Registry", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"reg-id\", data.id);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"url\": \"string\",\n \"isPublic\": true,\n \"username\": \"string\",\n \"password\": \"string\",\n \"email\": \"test@gmail.com\",\n \"requiresCert\": false,\n \"certificate\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/registries", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "registries" - ] - } - }, - "response": [] - }, - { - "name": "Update Registry", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"url\": \"string2\",\n \"isPublic\": true,\n \"username\": \"string3\",\n \"password\": \"string4\",\n \"email\": \"test2@gmail.com\",\n \"requiresCert\": true,\n \"certificate\": \"string6\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/registries/{{reg-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "registries", - "{{reg-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Registries", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('registries');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/registries", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "registries" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Registry", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/registries/{{reg-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "registries", - "{{reg-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Registries collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Capabilities", - "item": [ - { - "name": "Controller is Application Template capable", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "HEAD", - "header": [], - "url": { - "raw": "{{host}}/api/v3/capabilities/applicationTemplates", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "capabilities", - "applicationTemplates" - ] - } - }, - "response": [] - } - ] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ], - "variable": [ - { - "key": "host", - "value": "127.0.0.1:51121", - "type": "string" - } - ] -} From a6da7a96a8e77dc351f8514f70ed2306d2773917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 23:26:01 +0300 Subject: [PATCH 56/75] Update npm dependencies and security overrides. Pin uuid for the sequelize advisory, refresh the lockfile, fix empty audit config, and bump standard/mocha while dropping unused newman tooling. --- .nsprc | 1 + package-lock.json | 4836 +++++++++++---------------------------------- 2 files changed, 1116 insertions(+), 3721 deletions(-) diff --git a/.nsprc b/.nsprc index e69de29b..9e26dfee 100644 --- a/.nsprc +++ b/.nsprc @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 96105e6f..e47273b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,19 +76,16 @@ "chai": "5.1.1", "chai-as-promised": "7.1.2", "chai-http": "4.4.0", - "eslint": "9.28.0", - "eslint-config-google": "0.14.0", + "eslint": "8.57.1", "js-yaml": "^4.1.1", - "mocha": "10.6.0", + "mocha": "11.7.6", "mocha-junit-reporter": "2.2.1", - "newman": "^6.2.1", - "newman-reporter-junitfull": "1.1.1", "nyc": "15.1.0", "sequelize-cli": "6.6.2", "sinon": "17.0.1", "sinon-chai": "3.7.0", "snyk": "^1.1291.0", - "standard": "12.0.1" + "standard": "17.1.2" }, "engines": { "node": "^24.0.0" @@ -1022,17 +1019,6 @@ "node": ">=6.9.0" } }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/@datasance/ecn-viewer": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/@datasance/ecn-viewer/-/ecn-viewer-1.4.4.tgz", @@ -1089,19 +1075,6 @@ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", @@ -1112,88 +1085,25 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", - "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/config-array/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/config-helpers": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", - "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.14.0", + "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -1225,63 +1135,15 @@ "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.15.2", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@faker-js/faker": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz", - "integrity": "sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==", - "deprecated": "Please update to a newer version.", - "dev": true, - "license": "MIT" - }, "node_modules/@google-cloud/secret-manager": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/@google-cloud/secret-manager/-/secret-manager-6.1.3.tgz", @@ -1325,43 +1187,46 @@ "node": ">=6" } }, - "node_modules/@humanfs/core": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", - "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/types": "^0.15.0" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">=18.18.0" + "node": ">=10.10.0" } }, - "node_modules/@humanfs/node": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", - "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@humanfs/core": "^0.19.2", - "@humanfs/types": "^0.15.0", - "@humanwhocodes/retry": "^0.4.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=18.18.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@humanfs/types": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", - "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "node_modules/@humanwhocodes/config-array/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } + "license": "MIT" }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", @@ -1377,19 +1242,13 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } + "license": "BSD-3-Clause" }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -2126,6 +1985,44 @@ "node": ">= 10" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@one-ini/wasm": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", @@ -3016,50 +2913,6 @@ "node": ">=14" } }, - "node_modules/@postman/form-data": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", - "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@postman/tough-cookie": { - "version": "4.1.3-postman.1", - "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz", - "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@postman/tunnel-agent": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.8.tgz", - "integrity": "sha512-2U42SmZW5G+suEcS++zB94sBWNO4qD4bvETGFRFDTqSpYl5ksfjcPqzYpgQgXgUmb6dfz+fAGbkcRamounGm0w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -3123,6 +2976,13 @@ "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", "license": "BSD-3-Clause" }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, "node_modules/@scure/base": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.2.0.tgz", @@ -3419,23 +3279,16 @@ "@types/ms": "*" } }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/js-yaml": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", "license": "MIT" }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true, "license": "MIT" }, @@ -3504,6 +3357,13 @@ "node": ">=20.0.0" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "dev": true, + "license": "ISC" + }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", @@ -3608,36 +3468,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -3662,20 +3492,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/anynum": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.0.tgz", @@ -3775,16 +3591,18 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" }, "engines": { @@ -3794,20 +3612,20 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -3816,32 +3634,89 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT" - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": "~2.1.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, "engines": { - "node": ">=0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -3909,23 +3784,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", - "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", - "dev": true, - "license": "MIT" - }, "node_modules/b4a": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", @@ -3940,95 +3798,6 @@ } } }, - "node_modules/babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, - "node_modules/babel-code-frame/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/babel-code-frame/node_modules/js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", - "dev": true, - "license": "MIT" - }, - "node_modules/babel-code-frame/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -4162,23 +3931,6 @@ "node": ">=6.0.0" } }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/bcrypt-pbkdf/node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "license": "Unlicense" - }, "node_modules/bdd-lazy-var": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/bdd-lazy-var/-/bdd-lazy-var-2.6.1.tgz", @@ -4215,19 +3967,6 @@ "node": "*" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -4271,13 +4010,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/bluebird": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", - "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==", - "dev": true, - "license": "MIT" - }, "node_modules/body-parser": { "version": "1.20.5", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", @@ -4328,29 +4060,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brotli": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", - "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "base64-js": "^1.1.2" - } - }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -4428,6 +4137,16 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/builtins": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", @@ -4527,29 +4246,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha512-UJiE1otjXPF5/x+T3zTnSFiTOEmJoGTD9HmBoxnCUwho61a2eSNn/VwtwuIBDAo2SEOv1AJ7ARI5gCmohFLu/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/caller-path/node_modules/callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha512-Zv4Dns9IbXXmPkgRRUjAaJQgfN4xX5p6+RQFhWUqscdvvK2xK/ZL8b3IXIJsj+4sD+f24NwnWy2BY8AJ82JB0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4603,13 +4299,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/chai": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", @@ -4704,13 +4393,6 @@ "url": "https://github.com/chalk/chalk-template?sponsor=1" } }, - "node_modules/chardet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.0.0.tgz", - "integrity": "sha512-xVgPpulCooDjY6zH4m9YW3jbkaBe3FKIAvF5sj5t7aBNsVl2ljIE+xwJ4iNgiDZHFQvNIpjdKdVOQvvk5ZfxbQ==", - "dev": true, - "license": "MIT" - }, "node_modules/charenc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", @@ -4742,41 +4424,19 @@ } }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" } }, "node_modules/chownr": { @@ -4788,14 +4448,6 @@ "node": ">=18" } }, - "node_modules/circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "deprecated": "CircularJSON is in maintenance only, flatted is its successor.", - "dev": true, - "license": "MIT" - }, "node_modules/cjs-module-lexer": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", @@ -4829,55 +4481,6 @@ "node": ">=0.10" } }, - "node_modules/cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-progress": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", - "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "dev": true, - "license": "ISC" - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -4910,16 +4513,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4980,16 +4573,6 @@ "node": ">=12.17" } }, - "node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -5097,16 +4680,6 @@ "dev": true, "license": "ISC" }, - "node_modules/contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha512-OKZnPGeMQy2RPaUIBPFFd71iNf4791H12MCRuVQDnzGRwCYNYmTDy5pdafo2SLAcEMKzTOQnLWG4QdcjeJUMEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -5226,13 +4799,6 @@ "node": "*" } }, - "node_modules/csv-parse": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", - "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", - "dev": true, - "license": "MIT" - }, "node_modules/d": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", @@ -5255,19 +4821,6 @@ "node": ">0.8.x" } }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -5340,16 +4893,6 @@ "ms": "2.0.0" } }, - "node_modules/debug-log": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", - "integrity": "sha512-gV/pe1YIaKNgLYnd1g9VNW80tcb7oV5qvNUxG7NM8rbDpnl6RGunzlAtlGSb0wEs3nesu2vHNiX9TSsZ+Y+RjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -5508,50 +5051,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/deglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/deglob/-/deglob-2.1.1.tgz", - "integrity": "sha512-2kjwuGGonL7gWE1XU4Fv79+vVzpoQCl0V+boMwWtOQJV2AGDabCwez++nB1Nli/8BabAfZQ/UuHPlp6AymKdWw==", - "dev": true, - "license": "ISC", - "dependencies": { - "find-root": "^1.0.0", - "glob": "^7.0.5", - "ignore": "^3.0.9", - "pkg-config": "^1.1.0", - "run-parallel": "^1.1.2", - "uniq": "^1.0.1" - } - }, - "node_modules/deglob/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/deglob/node_modules/ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true, - "license": "MIT" - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -5585,17 +5084,6 @@ "node": ">= 0.8" } }, - "node_modules/des.js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", - "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -5633,9 +5121,9 @@ } }, "node_modules/diff": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", - "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -5729,17 +5217,6 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -5967,6 +5444,34 @@ "node": ">= 0.4" } }, + "node_modules/es-iterator-helpers": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.3.tgz", + "integrity": "sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-object-atoms": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", @@ -6117,77 +5622,115 @@ } }, "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.14.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", - "@eslint/plugin-kit": "^0.3.1", - "@humanfs/node": "^0.16.6", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", + "cross-spawn": "^7.0.2", "debug": "^4.3.2", + "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", + "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3" + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-google": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", - "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", + "node_modules/eslint-config-standard": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", "dev": true, - "license": "Apache-2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12.0.0" }, "peerDependencies": { - "eslint": ">=5.16.0" + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0" + } + }, + "node_modules/eslint-config-standard-jsx": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-11.0.0.tgz", + "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peerDependencies": { + "eslint": "^8.8.0", + "eslint-plugin-react": "^7.28.0" } }, "node_modules/eslint-import-resolver-node": { @@ -6279,145 +5822,275 @@ "license": "MIT" }, "node_modules/eslint-plugin-es": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz", - "integrity": "sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", "dev": true, "license": "MIT", "dependencies": { - "eslint-utils": "^1.4.2", - "regexpp": "^2.0.1" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" }, "engines": { - "node": ">=6.5.0" + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { "eslint": ">=4.19.1" } }, - "node_modules/eslint-plugin-node": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-7.0.1.tgz", - "integrity": "sha512-lfVw3TEqThwq0j2Ba/Ckn2ABdwmL5dkOgAux1rvOk6CO7A6yGyPI2+zIxN6FyNkp1X1X/BSvKOceD6mBWSj4Yw==", + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "license": "MIT", "dependencies": { - "eslint-plugin-es": "^1.3.1", - "eslint-utils": "^1.3.1", - "ignore": "^4.0.2", - "minimatch": "^3.0.4", - "resolve": "^1.8.1", - "semver": "^5.5.0" + "eslint-visitor-keys": "^1.1.0" }, "engines": { "node": ">=6" }, - "peerDependencies": { - "eslint": ">=4.19.1" + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/eslint-plugin-node/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, "engines": { - "node": ">= 4" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, - "node_modules/eslint-plugin-node/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-promise": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz", - "integrity": "sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg==", + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "engines": { - "node": ">=6" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/eslint-plugin-standard": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.2.tgz", - "integrity": "sha512-nKptN8l7jksXkwFk++PhJB3cCDTcXOEyhISIN86Ue2feJ1LFyY3PrY3/xT2keXlJSY5bpmbiTG0f885/YKAvTA==", + "node_modules/eslint-plugin-n": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", + "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT", + "dependencies": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.11.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, "peerDependencies": { - "eslint": ">=5.0.0" + "eslint": ">=7.0.0" } }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "node_modules/eslint-plugin-promise": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", + "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, + "license": "ISC", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" }, "engines": { - "node": ">=6" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -6441,6 +6114,19 @@ } } }, + "node_modules/eslint/node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/eslint/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6465,36 +6151,23 @@ } }, "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/espree/node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -6808,38 +6481,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/external-editor/node_modules/chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha512-j/Toj7f1z98Hh2cYo2BVr85EpIRWqUi7rtRSGxh/cqUjqrnJe9l9UE7IUGd2vQ2p+kSHLkSzObQPZPLUC6TQwg==", - "dev": true, - "license": "MIT" - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6911,6 +6552,16 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -6934,50 +6585,17 @@ "node": "^12.20 || >= 14.13" } }, - "node_modules/figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/file-type": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/file-uri-to-path": { @@ -6995,29 +6613,6 @@ "minimatch": "^5.0.1" } }, - "node_modules/filesize": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.4.tgz", - "integrity": "sha512-ryBwPIIeErmxgPnm6cbESAzXjuEFubs+yKYLBZvg3CaiNcmkJChoOGcBSrZ6IwkMwPABwPpVXE6IlNdGJJrvEg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 10.4.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/finalhandler": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", @@ -7066,13 +6661,6 @@ "node": ">=4.0.0" } }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "dev": true, - "license": "MIT" - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -7101,17 +6689,57 @@ } }, "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.4" + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=16" + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flat-cache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/flatted": { @@ -7151,16 +6779,6 @@ "node": ">=8.0.0" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -7299,21 +6917,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/ftp": { "version": "0.3.10", "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", @@ -7356,13 +6959,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true, - "license": "MIT" - }, "node_modules/functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -7548,13 +7144,16 @@ } }, "node_modules/get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/get-stream": { @@ -7587,16 +7186,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -7604,21 +7193,21 @@ "license": "MIT" }, "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">=12" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -7637,6 +7226,34 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/global-agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", @@ -7656,13 +7273,29 @@ } }, "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">=18" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7771,6 +7404,13 @@ "devOptional": true, "license": "ISC" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, "node_modules/gtoken": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", @@ -7784,86 +7424,6 @@ "node": ">=18" } }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -8003,13 +7563,6 @@ "node": ">=16.0.0" } }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" - }, "node_modules/hpagent": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", @@ -8129,63 +7682,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/http-reasons": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/http-reasons/-/http-reasons-0.1.0.tgz", - "integrity": "sha512-P6kYh0lKZ+y29T2Gqz+RlC9WBLhKe8kDmcJ+A+611jFfxdPsbMRQ5aNmFRM3lENqFkK+HTTL+tlQviAiv0AbLQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/http-signature": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", - "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.18.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/httpntlm": { - "version": "1.8.13", - "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.8.13.tgz", - "integrity": "sha512-2F2FDPiWT4rewPzNMg3uPhNkP3NExENlUGADRUDPQvuftuUTGW98nLZtGemCIW3G40VhWZYgkIDcQFAwZ3mf2Q==", - "dev": true, - "funding": [ - { - "type": "paypal", - "url": "https://www.paypal.com/donate/?hosted_button_id=2CKNJLZJBW8ZC" - }, - { - "type": "buymeacoffee", - "url": "https://www.buymeacoffee.com/samdecrock" - } - ], - "dependencies": { - "des.js": "^1.0.1", - "httpreq": ">=0.4.22", - "js-md4": "^0.3.2", - "underscore": "~1.12.1" - }, - "engines": { - "node": ">=10.4.0" - } - }, - "node_modules/httpreq": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-1.1.1.tgz", - "integrity": "sha512-uhSZLPPD2VXXOSN8Cni3kIsoFHaU2pT/nySEU/fHr/ePbqHYr0jeiQRmUKLEirC09SFPsdMoA7LU7UXMd/w0Kw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.15.1" - } - }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -8371,156 +7867,6 @@ "node": ">=10" } }, - "node_modules/inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.1.0", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^5.5.2", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/inquirer/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/inquirer/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/inquirer/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -8637,19 +7983,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-boolean-object": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", @@ -8891,16 +8224,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-number-like": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", @@ -8927,6 +8250,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -8969,13 +8302,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true, - "license": "ISC" - }, "node_modules/is-root": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", @@ -9186,13 +8512,6 @@ "ws": "*" } }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true, - "license": "MIT" - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -9384,14 +8703,32 @@ "node": ">=8" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -9447,58 +8784,6 @@ "node": ">=14" } }, - "node_modules/js-beautify/node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/js-beautify/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/js-beautify/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/js-cookie": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", @@ -9506,20 +8791,6 @@ "dev": true, "license": "MIT" }, - "node_modules/js-md4": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", - "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-sha512": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.9.0.tgz", - "integrity": "sha512-mirki9WS/SUahm+1TbAPkqvbCiCfOAAsyXeHxK1UkullnJVVqoJG2pL9ObvT05CN+tM7fxhfYm0NbXn+1hWoZg==", - "dev": true, - "license": "MIT" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9549,13 +8820,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, - "license": "MIT" - }, "node_modules/jsep": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", @@ -9599,13 +8863,6 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "license": "MIT" }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -9718,31 +8975,17 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/jsprim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, "node_modules/jsx-ast-utils": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", - "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, "license": "MIT", "dependencies": { - "array-includes": "^3.1.1", - "object.assign": "^4.1.0" + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" }, "engines": { "node": ">=4.0" @@ -9935,30 +9178,21 @@ "immediate": "~3.0.5" } }, - "node_modules/liquid-json": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/liquid-json/-/liquid-json-0.3.1.tgz", - "integrity": "sha512-wUayTU8MS827Dam6MxgD72Ui+KOSF+u/eIqpatOtjnvgJ0+mnDq33uC2M7J0tPK+upe/DpUAuK4JUU89iBoNKQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=4" - } - }, "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.1.15", "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/load-json-file/node_modules/strip-bom": { @@ -9971,6 +9205,16 @@ "node": ">=4" } }, + "node_modules/load-json-file/node_modules/type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=6" + } + }, "node_modules/localforage": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", @@ -10276,16 +9520,6 @@ "node": ">= 0.6" } }, - "node_modules/mime-format": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mime-format/-/mime-format-2.0.1.tgz", - "integrity": "sha512-XxU3ngPbEnrYnNbIX+lYSaYg0M01v6p2ntd2YaFksTu0vayaw5OJvbdRyWs07EYRlLED5qadUZ+xo+XhOvFhwg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "charset": "^1.0.0" - } - }, "node_modules/mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", @@ -10298,16 +9532,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -10320,13 +9544,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "license": "ISC" - }, "node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -10395,31 +9612,32 @@ "license": "MIT" }, "node_modules/mocha": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.6.0.tgz", - "integrity": "sha512-hxjt4+EEB0SA0ZDygSS015t65lJw/I2yRCS3Ae+SJ5FrbzrXgfYwJr96f0OvIXdj7h4lv/vLCrH3rkiuizFSvw==", + "version": "11.7.6", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.6.tgz", + "integrity": "sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", + "chokidar": "^4.0.1", "debug": "^4.3.5", - "diff": "^5.2.0", + "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", - "glob": "^8.1.0", + "glob": "^10.4.5", "he": "^1.2.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", + "minimatch": "^9.0.5", "ms": "^2.1.3", + "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" }, "bin": { @@ -10427,7 +9645,7 @@ "mocha": "bin/mocha.js" }, "engines": { - "node": ">= 14.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/mocha-junit-reporter": { @@ -10472,18 +9690,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, "node_modules/mocha/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -10525,23 +9731,14 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/mocha/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, + "license": "ISC", "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/module-details-from-path": { @@ -10617,13 +9814,6 @@ "node": ">=0.10.0" } }, - "node_modules/mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==", - "dev": true, - "license": "ISC" - }, "node_modules/mysql2": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.10.1.tgz", @@ -10751,97 +9941,6 @@ "node": ">= 0.6" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/newman": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/newman/-/newman-6.2.2.tgz", - "integrity": "sha512-BmGzMz6f2FLtw/hHAbhEAVqXS+3APJGAWzlxVijSElFaxC37wpHEqsOB09d/2uHMvTyMXGArtbFa+z5m/a68Uw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@postman/tough-cookie": "4.1.3-postman.1", - "async": "3.2.5", - "chardet": "2.0.0", - "cli-progress": "3.12.0", - "cli-table3": "0.6.5", - "colors": "1.4.0", - "commander": "11.1.0", - "csv-parse": "4.16.3", - "filesize": "10.1.4", - "liquid-json": "0.3.1", - "lodash": "4.17.21", - "mkdirp": "3.0.1", - "postman-collection": "4.4.0", - "postman-collection-transformer": "4.1.8", - "postman-request": "2.88.1-postman.48", - "postman-runtime": "7.39.1", - "pretty-ms": "7.0.1", - "semver": "7.6.3", - "serialised-error": "1.1.3", - "word-wrap": "1.2.5", - "xmlbuilder": "15.1.1" - }, - "bin": { - "newman": "bin/newman.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/newman-reporter-junitfull": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/newman-reporter-junitfull/-/newman-reporter-junitfull-1.1.1.tgz", - "integrity": "sha512-ET5rU1qkeJ5yvFxcKQFkqGxWia50kdnufm1uzyeNYlUg6T+k07AvOS0mfp/Ejr0njnsiPfFLb9kC48F8pafq9A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "lodash": "^4.17.10", - "moment": "^2.22.2", - "xmlbuilder": "^10.0.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "newman": ">=4" - } - }, - "node_modules/newman-reporter-junitfull/node_modules/xmlbuilder": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", - "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/newman/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "license": "MIT" - }, - "node_modules/newman/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", @@ -11067,13 +10166,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/node-oauth1": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/node-oauth1/-/node-oauth1-1.3.0.tgz", - "integrity": "sha512-0yggixNfrA1KcBwvh/Hy2xAS1Wfs9dcg6TdFf2zN7gilcAigMdrtZ4ybrBSXBgLvGDw9V1p2MRnGBMq7XjTWLg==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -11113,39 +10205,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -11385,16 +10444,6 @@ "node": ">=6" } }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, "node_modules/oauth4webapi": { "version": "3.8.6", "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.6.tgz", @@ -11481,24 +10530,77 @@ "node": ">= 0.4" } }, - "node_modules/oidc-provider": { - "version": "9.8.4", - "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-9.8.4.tgz", - "integrity": "sha512-i8qe+wvhUQ7BSj6DxssIFAdpREouuqK91j2jGdAN78NIxTB5rxvU4ZniXELhUHIM4mzABeSGlp5wE6WTa8CY1Q==", + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, "license": "MIT", "dependencies": { - "@koa/cors": "^5.0.0", - "@koa/router": "^15.5.0", - "debug": "^4.4.3", - "eta": "^4.6.0", - "jose": "^6.2.3", - "jsesc": "^3.1.0", - "koa": "^3.2.1", - "nanoid": "^5.1.11", - "quick-lru": "^7.3.0", - "raw-body": "^3.0.2" - }, - "funding": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/oidc-provider": { + "version": "9.8.4", + "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-9.8.4.tgz", + "integrity": "sha512-i8qe+wvhUQ7BSj6DxssIFAdpREouuqK91j2jGdAN78NIxTB5rxvU4ZniXELhUHIM4mzABeSGlp5wE6WTa8CY1Q==", + "license": "MIT", + "dependencies": { + "@koa/cors": "^5.0.0", + "@koa/router": "^15.5.0", + "debug": "^4.4.3", + "eta": "^4.6.0", + "jose": "^6.2.3", + "jsesc": "^3.1.0", + "koa": "^3.2.1", + "nanoid": "^5.1.11", + "quick-lru": "^7.3.0", + "raw-body": "^3.0.2" + }, + "funding": { "url": "https://github.com/sponsors/panva" } }, @@ -11610,19 +10712,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/open": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", @@ -11681,16 +10770,6 @@ "node": ">= 0.8.0" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/otplib": { "version": "13.4.1", "resolved": "https://registry.npmjs.org/otplib/-/otplib-13.4.1.tgz", @@ -11836,16 +10915,6 @@ "node": ">=4" } }, - "node_modules/parse-ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", - "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -11890,13 +10959,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "dev": true, - "license": "(WTFPL OR MIT)" - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -11941,29 +11003,6 @@ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, - "node_modules/path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha512-dUnb5dXUf+kzhC/W/F4e5/SkluXIFf5VUHolW1Eg1irn1hGWjPGdsRcvYJ1nD6lhk8Ir7VM0bHJKsYTx8Jx9OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-type/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pathval": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", @@ -11974,13 +11013,6 @@ "node": ">= 14.16" } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true, - "license": "MIT" - }, "node_modules/pg": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz", @@ -12076,27 +11108,14 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/pino": { @@ -12137,80 +11156,73 @@ "license": "MIT" }, "node_modules/pkg-conf": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", - "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", + "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", "dev": true, "license": "MIT", "dependencies": { - "find-up": "^2.0.0", - "load-json-file": "^4.0.0" + "find-up": "^3.0.0", + "load-json-file": "^5.2.0" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/pkg-conf/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^2.0.0" + "locate-path": "^3.0.0" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/pkg-conf/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^2.0.0", + "p-locate": "^3.0.0", "path-exists": "^3.0.0" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/pkg-conf/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/pkg-conf/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^1.1.0" + "p-limit": "^2.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-conf/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/pkg-conf/node_modules/path-exists": { @@ -12223,21 +11235,6 @@ "node": ">=4" } }, - "node_modules/pkg-config": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pkg-config/-/pkg-config-1.1.1.tgz", - "integrity": "sha512-ft/WI9YK6FuTuw4Ql+QUaNXtm/ASQNqDUUsZEgFZKyFpW6amyP8Gx01xrRs8KdiNbbqXfYxkOXplpq1euWbOjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug-log": "^1.0.0", - "find-root": "^1.0.0", - "xtend": "^4.0.1" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -12307,16 +11304,6 @@ "node": ">=8" } }, - "node_modules/pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/portscanner": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", @@ -12389,455 +11376,100 @@ "node": ">=0.10.0" } }, - "node_modules/postman-collection": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.4.0.tgz", - "integrity": "sha512-2BGDFcUwlK08CqZFUlIC8kwRJueVzPjZnnokWPtJCd9f2J06HBQpGL7t2P1Ud1NEsK9NHq9wdipUhWLOPj5s/Q==", - "dev": true, - "license": "Apache-2.0", + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", "dependencies": { - "@faker-js/faker": "5.5.3", - "file-type": "3.9.0", - "http-reasons": "0.1.0", - "iconv-lite": "0.6.3", - "liquid-json": "0.3.1", - "lodash": "4.17.21", - "mime-format": "2.0.1", - "mime-types": "2.1.35", - "postman-url-encoder": "3.0.5", - "semver": "7.5.4", - "uuid": "8.3.2" + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" }, "engines": { "node": ">=10" } }, - "node_modules/postman-collection-transformer": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/postman-collection-transformer/-/postman-collection-transformer-4.1.8.tgz", - "integrity": "sha512-smJ6X7Z7kbg6hp7JZPFixrSN3J3WkQed7DrWCC5tF7IxOMpFLqhtTtGssY8nD1inP8+mJf+N72Pf2ttUAHgBKw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/prebuild-install/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/prebuild-install/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { - "commander": "8.3.0", - "inherits": "2.0.4", - "lodash": "4.17.21", - "semver": "7.5.4", - "strip-json-comments": "3.1.1" - }, - "bin": { - "postman-collection-transformer": "bin/transform-collection.js" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=10" + "node": ">= 6" } }, - "node_modules/postman-collection-transformer/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, + "node_modules/prebuild-install/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", - "engines": { - "node": ">= 12" + "dependencies": { + "safe-buffer": "~5.2.0" } }, - "node_modules/postman-collection-transformer/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" } }, - "node_modules/postman-collection-transformer/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "license": "ISC", + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" }, "engines": { - "node": ">=10" + "node": ">=6" } }, - "node_modules/postman-collection-transformer/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/postman-collection/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postman-collection/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/postman-collection/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/postman-collection/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/postman-request": { - "version": "2.88.1-postman.48", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.48.tgz", - "integrity": "sha512-E32FGh8ig2KDvzo4Byi7Ibr+wK2gNKPSqXoNsvjdCHgDBxSK4sCUwv+aa3zOBUwfiibPImHMy0WdlDSSCTqTuw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@postman/form-data": "~3.1.1", - "@postman/tough-cookie": "~4.1.3-postman.1", - "@postman/tunnel-agent": "^0.6.8", - "aws-sign2": "~0.7.0", - "aws4": "^1.12.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "http-signature": "~1.4.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "^2.1.35", - "oauth-sign": "~0.9.0", - "qs": "~6.14.1", - "safe-buffer": "^5.1.2", - "socks-proxy-agent": "^8.0.5", - "stream-length": "^1.0.2", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">= 16" - } - }, - "node_modules/postman-request/node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/postman-runtime": { - "version": "7.39.1", - "resolved": "https://registry.npmjs.org/postman-runtime/-/postman-runtime-7.39.1.tgz", - "integrity": "sha512-IRNrBE0l1K3ZqQhQVYgF6MPuqOB9HqYncal+a7RpSS+sysKLhJMkC9SfUn1HVuOpokdPkK92ykvPzj8kCOLYAg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@postman/tough-cookie": "4.1.3-postman.1", - "async": "3.2.5", - "aws4": "1.12.0", - "handlebars": "4.7.8", - "httpntlm": "1.8.13", - "jose": "4.14.4", - "js-sha512": "0.9.0", - "lodash": "4.17.21", - "mime-types": "2.1.35", - "node-forge": "1.3.1", - "node-oauth1": "1.3.0", - "performance-now": "2.1.0", - "postman-collection": "4.4.0", - "postman-request": "2.88.1-postman.34", - "postman-sandbox": "4.7.1", - "postman-url-encoder": "3.0.5", - "serialised-error": "1.1.3", - "strip-json-comments": "3.1.1", - "uuid": "8.3.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/postman-runtime/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "license": "MIT" - }, - "node_modules/postman-runtime/node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true, - "license": "MIT" - }, - "node_modules/postman-runtime/node_modules/http-signature": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", - "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.14.1" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/postman-runtime/node_modules/jose": { - "version": "4.14.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", - "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/postman-runtime/node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true, - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/postman-runtime/node_modules/postman-request": { - "version": "2.88.1-postman.34", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.34.tgz", - "integrity": "sha512-GkolJ4cIzgamcwHRDkeZc/taFWO1u2HuGNML47K9ZAsFH2LdEkS5Yy8QanpzhjydzV3WWthl9v60J8E7SjKodQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@postman/form-data": "~3.1.1", - "@postman/tough-cookie": "~4.1.3-postman.1", - "@postman/tunnel-agent": "^0.6.3", - "aws-sign2": "~0.7.0", - "aws4": "^1.12.0", - "brotli": "^1.3.3", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "har-validator": "~5.1.3", - "http-signature": "~1.3.1", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "^2.1.35", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.3", - "safe-buffer": "^5.1.2", - "stream-length": "^1.0.2", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/postman-runtime/node_modules/qs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz", - "integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/postman-sandbox": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/postman-sandbox/-/postman-sandbox-4.7.1.tgz", - "integrity": "sha512-H2wYSLK0mB588IaxoLrLoPbpmxsIcwFtgaK2c8gAsAQ+TgYFePwb4qdeVcYDMqmwrLd77/ViXkjasP/sBMz1sQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "lodash": "4.17.21", - "postman-collection": "4.4.0", - "teleport-javascript": "1.0.0", - "uvm": "2.1.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/postman-url-encoder": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-3.0.5.tgz", - "integrity": "sha512-jOrdVvzUXBC7C+9gkIkpDJ3HIxOHTIqjpQ4C1EMt1ZGeMvSEpbFCKq23DEfgsj46vMnDgyQf+1ZLp2Wm+bKSsA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prebuild-install/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/prebuild-install/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prebuild-install/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/prebuild-install/node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/prebuild-install/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" } }, - "node_modules/pretty-ms": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", - "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-ms": "^2.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/proc-log": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", @@ -12883,16 +11515,6 @@ ], "license": "MIT" }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/promise-polyfill": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-2.1.4.tgz", @@ -12977,19 +11599,6 @@ "node": ">= 0.10" } }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, "node_modules/pump": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", @@ -13025,13 +11634,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "license": "MIT" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -13080,16 +11682,6 @@ "node": ">= 0.8" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -13151,161 +11743,10 @@ "dev": true, "license": "MIT" }, - "node_modules/read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha512-eFIBOPW7FGjzBuk3hdXEuNSiTZS/xEMlH49HxMyzb0hyPfu4EhVjT2DH32K1hSSmVq4sebAWnZuuY5auISUTGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha512-1orxQfbWGUiTn9XsPlChs6rLie/AV9jwZTGmu2NZw/CUDJQchXJFYE0Fq5j7+n558T1JhDWLdhyd1Zj+wLY//w==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg/node_modules/load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg/node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -13315,16 +11756,17 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/real-require": { @@ -13381,13 +11823,16 @@ } }, "node_modules/regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.5.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, "node_modules/release-zalgo": { @@ -13455,37 +11900,6 @@ "dev": true, "license": "ISC" }, - "node_modules/require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha512-Xct+41K3twrbBHdxAgMoOS+cNcoqIjfM2/VxBF4LL2hVph7YsF8VSKyQ3BDFZwEVbok9yeDl2le/qo0S77WG2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-uncached/node_modules/resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha512-kT10v4dhrlLNcnO084hEjvXCI1wUG9qZLoz2RogxqDQQYy7IxjI/iMUkOtQTNEh6rzHxvdQWHsJyel1pKOVCxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", @@ -13518,20 +11932,6 @@ "node": ">=4" } }, - "node_modules/restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/retry-as-promised": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.1.1.tgz", @@ -13551,6 +11951,17 @@ "node": ">=18" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rfc4648": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.4.tgz", @@ -13604,55 +12015,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -13683,16 +12045,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -13717,19 +12069,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "5.5.12", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", - "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "symbol-observable": "1.0.1" - }, - "engines": { - "npm": ">=2.0.0" - } - }, "node_modules/safe-array-concat": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", @@ -14042,39 +12381,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/serialised-error": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/serialised-error/-/serialised-error-1.1.3.tgz", - "integrity": "sha512-vybp3GItaR1ZtO2nxZZo8eOo7fnVaNtP3XE2vJKgzkKR2bagCkdJ1EpYYhEMd3qu/80DwQk9KjsNSxE3fXWq0g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "object-hash": "^1.1.2", - "stack-trace": "0.0.9", - "uuid": "^3.0.0" - } - }, - "node_modules/serialised-error/node_modules/object-hash": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", - "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/serialised-error/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/serialize-error": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", @@ -14105,13 +12411,13 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/serve-static": { @@ -14371,29 +12677,6 @@ "sinon": ">=4.0.0" } }, - "node_modules/slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/slow-redact": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/slow-redact/-/slow-redact-0.3.2.tgz", @@ -14555,42 +12838,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -14643,674 +12890,72 @@ "node": ">= 0.6" } }, - "node_modules/sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sshpk/node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "license": "Unlicense" - }, - "node_modules/stack-trace": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", - "integrity": "sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/standard": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/standard/-/standard-12.0.1.tgz", - "integrity": "sha512-UqdHjh87OG2gUrNCSM4QRLF5n9h3TFPwrCNyVlkqu31Hej0L/rc8hzKqVvkb2W3x0WMq7PzZdkLfEcBhVOR6lg==", + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/standard/-/standard-17.1.2.tgz", + "integrity": "sha512-WLm12WoXveKkvnPnPnaFUUHuOB2cUdAsJ4AiGHL2G0UNMrcRAWY2WriQaV8IQ3oRmYr0AWUbLNr94ekYFAHOrA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "dependencies": { - "eslint": "~5.4.0", - "eslint-config-standard": "12.0.0", - "eslint-config-standard-jsx": "6.0.2", - "eslint-plugin-import": "~2.14.0", - "eslint-plugin-node": "~7.0.1", - "eslint-plugin-promise": "~4.0.0", - "eslint-plugin-react": "~7.11.1", - "eslint-plugin-standard": "~4.0.0", - "standard-engine": "~9.0.0" + "eslint": "^8.41.0", + "eslint-config-standard": "17.1.0", + "eslint-config-standard-jsx": "^11.0.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-n": "^15.7.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-react": "^7.36.1", + "standard-engine": "^15.1.0", + "version-guard": "^1.1.1" }, "bin": { - "standard": "bin/cmd.js" + "standard": "bin/cmd.cjs" }, "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/standard-engine": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-9.0.0.tgz", - "integrity": "sha512-ZfNfCWZ2Xq67VNvKMPiVMKHnMdvxYzvZkf1AH8/cw2NLDBm5LRsxMqvEJpsjLI/dUosZ3Z1d6JlHDp5rAvvk2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "deglob": "^2.1.0", - "get-stdin": "^6.0.0", - "minimist": "^1.1.0", - "pkg-conf": "^2.0.0" - } - }, - "node_modules/standard/node_modules/acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/standard/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/standard/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/standard/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/standard/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/standard/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/standard/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/standard/node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/standard/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/standard/node_modules/eslint": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.4.0.tgz", - "integrity": "sha512-UIpL91XGex3qtL6qwyCQJar2j3osKxK9e3ano3OcGEIRM4oWIpCkDg9x95AXEC2wMs7PnxzOkPZ2gq+tsMS9yg==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.5.0", - "babel-code-frame": "^6.26.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^4.0.0", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^4.0.0", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.2", - "imurmurhash": "^0.1.4", - "inquirer": "^5.2.0", - "is-resolvable": "^1.1.0", - "js-yaml": "^3.11.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.5", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", - "progress": "^2.0.0", - "regexpp": "^2.0.0", - "require-uncached": "^1.0.3", - "semver": "^5.5.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^4.0.3", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^6.14.0 || ^8.10.0 || >=9.10.0" - } - }, - "node_modules/standard/node_modules/eslint-config-standard": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz", - "integrity": "sha512-COUz8FnXhqFitYj4DTqHzidjIL/t4mumGZto5c7DrBpvWoie+Sn3P4sLEzUGeYhRElWuFEf8K1S1EfvD1vixCQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=5.0.0", - "eslint-plugin-import": ">=2.13.0", - "eslint-plugin-node": ">=7.0.0", - "eslint-plugin-promise": ">=4.0.0", - "eslint-plugin-standard": ">=4.0.0" - } - }, - "node_modules/standard/node_modules/eslint-config-standard-jsx": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-6.0.2.tgz", - "integrity": "sha512-D+YWAoXw+2GIdbMBRAzWwr1ZtvnSf4n4yL0gKGg7ShUOGXkSOLerI17K4F6LdQMJPNMoWYqepzQD/fKY+tXNSg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=5.0.0", - "eslint-plugin-react": ">=7.11.1" - } - }, - "node_modules/standard/node_modules/eslint-plugin-import": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", - "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "contains-path": "^0.1.0", - "debug": "^2.6.8", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.1", - "eslint-module-utils": "^2.2.0", - "has": "^1.0.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.3", - "read-pkg-up": "^2.0.0", - "resolve": "^1.6.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "2.x - 5.x" - } - }, - "node_modules/standard/node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha512-lsGyRuYr4/PIB0txi+Fy2xOMI2dGaTguCaotzFGkVZuKR5usKfcRWIFKNM3QNrU7hh/+w2bwTW+ZeXPK5l8uVg==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/standard/node_modules/eslint-plugin-react": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz", - "integrity": "sha512-cVVyMadRyW7qsIUh3FHp3u6QHNhOgVrLQYdQEB1bPWBsgbNCHdFAeNMquBMCcZJu59eNthX053L70l7gRt4SCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.0.3", - "doctrine": "^2.1.0", - "has": "^1.0.3", - "jsx-ast-utils": "^2.0.1", - "prop-types": "^15.6.2" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0" - } - }, - "node_modules/standard/node_modules/eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/standard/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/standard/node_modules/eslint/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/standard/node_modules/espree": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-4.1.0.tgz", - "integrity": "sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^6.0.2", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/standard/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/standard/node_modules/file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha512-uXP/zGzxxFvFfcZGgBIwotm+Tdc55ddPAzF7iHshP4YGaXMww7rSF9peD9D1sui5ebONg5UobsZv+FfgEpGv/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/standard/node_modules/flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/standard/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/standard/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/standard/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/standard/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/standard/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/standard/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/standard/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/standard/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/standard/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/standard/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/standard/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/standard/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/standard/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/standard/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/standard/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/standard/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/standard/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/standard/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/standard/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/standard/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/standard/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-15.1.0.tgz", + "integrity": "sha512-VHysfoyxFu/ukT+9v49d4BRXIokFRZuH3z1VRxzFArZdjSCFpro6rEIU3ji7e4AoAtuSfKBkiOmsrDqKW5ZSRw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "dependencies": { - "prelude-ls": "~1.1.2" + "get-stdin": "^8.0.0", + "minimist": "^1.2.6", + "pkg-conf": "^3.1.0", + "xdg-basedir": "^4.0.0" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/standard/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/statuses": { @@ -15354,16 +12999,6 @@ "stubs": "^3.0.0" } }, - "node_modules/stream-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz", - "integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==", - "dev": true, - "license": "WTFPL", - "dependencies": { - "bluebird": "^2.6.2" - } - }, "node_modules/stream-shift": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", @@ -15427,7 +13062,46 @@ "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" } }, "node_modules/string.prototype.trim": { @@ -15680,34 +13354,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha512-Kb3PrPYz4HanVF1LVGuAdW6LoVgIwjUYJGzFe7NDrBLCN4lsV/5J0MFurV+ygS4bRVwrCEt2c7MQ1R2a72oJDw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/table": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", - "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^6.0.1", - "ajv-keywords": "^3.0.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/table-layout": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz", @@ -15730,131 +13376,6 @@ "node": ">=12.17" } }, - "node_modules/table/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/table/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/table/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/table/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/tar": { "version": "7.5.16", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", @@ -15939,13 +13460,6 @@ "streamx": "^2.12.5" } }, - "node_modules/teleport-javascript": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/teleport-javascript/-/teleport-javascript-1.0.0.tgz", - "integrity": "sha512-j1llvWVFyEn/6XIFDfX5LAU43DXe0GCt3NfXDwJ8XpRRMkS+i50SAkonAONBy+vxwPFBd50MFU8a2uj8R/ccLg==", - "dev": true, - "license": "ISC" - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -16008,13 +13522,6 @@ "real-require": "^0.2.0" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true, - "license": "MIT" - }, "node_modules/timers-ext": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", @@ -16077,32 +13584,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -16124,6 +13605,42 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -16313,20 +13830,6 @@ "node": ">=8" } }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -16378,13 +13881,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/underscore": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", - "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", - "dev": true, - "license": "MIT" - }, "node_modules/undici": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz", @@ -16401,23 +13897,6 @@ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, - "node_modules/uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==", - "dev": true, - "license": "MIT" - }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -16468,17 +13947,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -16495,44 +13963,15 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "license": "MIT", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/uvm": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/uvm/-/uvm-2.1.1.tgz", - "integrity": "sha512-BZ5w8adTpNNr+zczOBRpaX/hH8UPKAf7fmCnidrcsqt3bn8KT9bDIfuS7hgRU9RXgiN01su2pwysBONY6w8W5w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "flatted": "3.2.6" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/uvm/node_modules/flatted": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", - "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/validator": { @@ -16553,28 +13992,16 @@ "node": ">= 0.8" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "node_modules/version-guard": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/version-guard/-/version-guard-1.1.3.tgz", + "integrity": "sha512-JwPr6erhX53EWH/HCSzfy1tTFrtPXUe927wdM1jqBBeYp1OM+qPHjWbsvv6pIBduqdgxxS+ScfG7S28pzyr2DQ==", "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "license": "0BSD", + "engines": { + "node": ">=0.10.48" } }, - "node_modules/verror/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "license": "MIT" - }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -16737,13 +14164,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/wordwrapjs": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.1.tgz", @@ -16754,9 +14174,9 @@ } }, "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", "dev": true, "license": "Apache-2.0" }, @@ -16801,19 +14221,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha512-CJ17OoULEKXpA5pef3qLj5AxTJ6mSt7g84he2WIskKwqFO4T97d5V7Tadl0DYDk7qyUOQD5WlUlOMChaYrhxeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", @@ -16827,19 +14234,6 @@ "typedarray-to-buffer": "^3.1.5" } }, - "node_modules/write/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ws": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", @@ -16876,6 +14270,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/xml": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", @@ -16898,16 +14302,6 @@ "node": ">=16.0.0" } }, - "node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0" - } - }, "node_modules/xregexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", From 7ae22818ea263c2ae042ad8ffe5cfd620f4d4f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Thu, 11 Jun 2026 23:26:41 +0300 Subject: [PATCH 57/75] Apply standard v17 lint fixes across the codebase. Remove legacy ESLint config, align style (shorthand properties, quoteless keys, indentation), and fix minor logic issues surfaced by the upgraded linter. --- .eslintrc.js | 31 -- package.json | 17 +- scripts/cli-tests.js | 8 +- scripts/coverage.js | 6 +- scripts/init.js | 8 +- scripts/preuninstall.js | 2 +- scripts/start-dev.js | 9 +- scripts/start.js | 8 +- scripts/stop.js | 8 +- scripts/test.js | 8 +- scripts/util.js | 23 +- src/cli/base-cli-handler.js | 4 +- src/cli/cli-data-types.js | 4 +- src/cli/config.js | 2 +- src/cli/iofog.js | 4 +- src/cli/microservice.js | 20 +- src/cli/start.js | 1 + src/config/env-mapping.js | 224 ++++----- src/config/index.js | 2 +- src/controllers/agent-controller.js | 2 +- src/controllers/application-controller.js | 28 +- .../application-template-controller.js | 16 +- src/controllers/catalog-controller.js | 10 +- src/controllers/config-controller.js | 6 +- src/controllers/config-map-controller.js | 2 +- src/controllers/controller.js | 4 +- src/controllers/iofog-controller.js | 24 +- src/controllers/microservices-controller.js | 72 +-- src/controllers/rbac-controller.js | 42 +- src/controllers/registry-controller.js | 10 +- src/controllers/router-controller.js | 4 +- src/controllers/secret-controller.js | 2 +- src/controllers/service-controller.js | 2 +- src/controllers/tunnel-controller.js | 4 +- src/controllers/user-controller.js | 34 +- src/daemon.js | 2 +- src/data/managers/application-manager.js | 21 +- .../managers/application-template-manager.js | 4 +- src/data/managers/base-manager.js | 55 +-- src/data/managers/catalog-item-manager.js | 10 +- src/data/managers/certificate-manager.js | 146 +++--- src/data/managers/config-map-manager.js | 8 +- src/data/managers/event-manager.js | 6 +- src/data/managers/fog-used-token-manager.js | 2 +- src/data/managers/iofog-manager.js | 26 +- src/data/managers/iofog-public-key-manager.js | 54 +-- src/data/managers/microservice-manager.js | 50 +- src/data/managers/nats-connection-manager.js | 4 +- .../managers/rbac-cache-version-manager.js | 2 +- .../managers/rbac-role-binding-manager.js | 2 +- src/data/managers/rbac-role-manager.js | 2 +- .../managers/rbac-service-account-manager.js | 2 +- .../managers/router-connection-manager.js | 4 +- src/data/managers/secret-manager.js | 2 +- src/data/managers/service-manager.js | 12 +- src/data/managers/volume-mapping-manager.js | 4 +- src/data/managers/volume-mounting-manager.js | 16 +- src/data/providers/database-provider.js | 14 +- src/data/providers/mysql.js | 12 +- src/data/providers/postgres.js | 12 +- src/decorators/authorization-decorator.js | 2 +- src/decorators/response-decorator.js | 9 +- src/decorators/transaction-decorator.js | 2 +- src/helpers/app-helper.js | 2 +- src/helpers/constants.js | 4 +- src/helpers/errors.js | 30 +- src/helpers/template-helper.js | 4 +- src/routes/agent.js | 40 +- src/routes/application.js | 22 +- src/routes/applicationTemplate.js | 16 +- src/routes/catalog.js | 10 +- src/routes/certificate.js | 22 +- src/routes/cluster.js | 8 +- src/routes/config.js | 6 +- src/routes/configMap.js | 14 +- src/routes/controller.js | 4 +- src/routes/event.js | 4 +- src/routes/iofog.js | 26 +- src/routes/microservices.js | 70 +-- src/routes/nats.js | 54 +-- src/routes/rbac.js | 42 +- src/routes/registries.js | 10 +- src/routes/router.js | 4 +- src/routes/secret.js | 14 +- src/routes/service.js | 14 +- src/routes/tunnel.js | 4 +- src/routes/user.js | 2 +- src/routes/volumeMount.js | 20 +- src/schemas/agent.js | 234 +++++----- src/schemas/application-template.js | 110 ++--- src/schemas/application.js | 98 ++-- src/schemas/catalog.js | 116 ++--- src/schemas/cluster-controller.js | 12 +- src/schemas/controller-register.js | 50 +- src/schemas/iofog.js | 408 ++++++++-------- src/schemas/microservice.js | 435 +++++++++--------- src/schemas/nats.js | 198 ++++---- src/schemas/registry.js | 58 +-- src/schemas/service.js | 4 +- src/schemas/tunnel.js | 20 +- src/schemas/volume-mount.js | 98 ++-- src/server.js | 4 +- src/services/agent-service.js | 16 +- src/services/application-service.js | 8 +- src/services/application-template-service.js | 6 +- src/services/catalog-service.js | 42 +- src/services/certificate-service.js | 68 ++- src/services/cluster-controller-service.js | 4 +- src/services/config-map-service.js | 6 +- src/services/controller-service.js | 10 +- src/services/event-service.js | 20 +- src/services/iofog-service.js | 48 +- src/services/microservices-service.js | 214 +++++---- src/services/nats-api-service.js | 2 +- src/services/nats-auth-service.js | 36 +- src/services/nats-service.js | 32 +- src/services/rbac-service.js | 22 +- src/services/registry-service.js | 12 +- src/services/router-service.js | 14 +- src/services/secret-service.js | 6 +- src/services/services-service.js | 4 +- src/services/tunnel-service.js | 4 +- src/services/volume-mount-service.js | 6 +- src/services/yaml-parser-service.js | 42 +- src/utils/cert.js | 306 ++++++------ src/vault/aws-secrets-manager-provider.js | 6 +- src/vault/google-secret-manager-provider.js | 6 +- src/vault/hashicorp-vault-provider.js | 2 +- src/websocket/log-session-manager.js | 8 +- src/websocket/server.js | 121 +++-- src/websocket/session-manager.js | 4 +- swagger.js | 2 +- test/src/services/iofog-service.test.js | 1 + 133 files changed, 2293 insertions(+), 2212 deletions(-) delete mode 100644 .eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 73c32ff1..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,31 +0,0 @@ -var OFF = 0, WARN = 1, ERROR = 2; - -module.exports = { - 'env': { - 'es6': true, - 'node': true, - }, - - 'extends': 'google', - 'rules': { - "linebreak-style": 0, - 'require-jsdoc': [OFF, { - 'require': { - 'FunctionDeclaration': true, - 'MethodDefinition': true, - 'ClassDeclaration': false - } - }], - 'max-len': [WARN, 132], - 'no-invalid-this': OFF, - 'no-multi-str': OFF, - 'semi': [ERROR, 'never'], - 'space-before-function-paren': OFF, - 'object-curly-spacing': ['error', 'always'], - }, - - 'parserOptions': { - 'sourceType': 'module', - 'ecmaVersion': 2017, - } -} \ No newline at end of file diff --git a/package.json b/package.json index e6063fde..e4a473cd 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,6 @@ "pretest": "npm run lint", "test": "node scripts/run-test.js test", "test:k8s-client": "node scripts/run-test.js test --grep 'k8s-client'", - "prepostman_test": "npm run lint", - "postman_test": "node scripts/run-test.js postmantest", "precli-tests": "npm run lint", "cli-tests": "node scripts/run-test.js cli-tests", "precoverage": "npm run lint", @@ -124,26 +122,22 @@ "chai": "5.1.1", "chai-as-promised": "7.1.2", "chai-http": "4.4.0", - "eslint": "9.28.0", - "eslint-config-google": "0.14.0", + "eslint": "8.57.1", "js-yaml": "^4.1.1", - "mocha": "10.6.0", + "mocha": "11.7.6", "mocha-junit-reporter": "2.2.1", - "newman": "^6.2.1", - "newman-reporter-junitfull": "1.1.1", "nyc": "15.1.0", "sequelize-cli": "6.6.2", "sinon": "17.0.1", "sinon-chai": "3.7.0", "snyk": "^1.1291.0", - "standard": "12.0.1" + "standard": "17.1.2" }, "files": [ "/scripts", "/src", "/test", "/docs", - ".eslintrc.js", ".jshintrc", ".snyk" ], @@ -156,7 +150,10 @@ "lodash": "^4.18.0", "bn.js": "^5.2.3", "minimatch": "10.2.5", + "uuid": "11.1.1", "dottie": "^2.0.7", - "ip-address": "^10.1.1" + "ip-address": "^10.1.1", + "serialize-javascript": "7.0.5", + "diff": "8.0.3" } } diff --git a/scripts/cli-tests.js b/scripts/cli-tests.js index 00ad07ed..26f587c5 100644 --- a/scripts/cli-tests.js +++ b/scripts/cli-tests.js @@ -21,8 +21,8 @@ const RouterService = require('../src/services/router-service') const options = { env: { - 'NODE_ENV': 'production', - 'PATH': process.env.PATH + NODE_ENV: 'production', + PATH: process.env.PATH }, encoding: 'ascii' } @@ -290,7 +290,7 @@ function responseHasFields (jsonResponse, fields) { try { const response = JSON.parse(jsonResponse) for (const field of fields) { - if (!response.hasOwnProperty(field)) { + if (!Object.hasOwn(response, field)) { testsFailed++ console.log('\'responseHasFields\' test failed with response: ' + JSON.stringify(response)) } @@ -351,5 +351,5 @@ async function cliTest () { } module.exports = { - cliTest: cliTest + cliTest } diff --git a/scripts/coverage.js b/scripts/coverage.js index ee8c0d20..bbe48842 100644 --- a/scripts/coverage.js +++ b/scripts/coverage.js @@ -18,8 +18,8 @@ const { setDbEnvVars } = require('./util') function coverage () { const options = { env: { - 'NODE_ENV': 'test', - 'PATH': process.env.PATH + NODE_ENV: 'test', + PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] } @@ -30,5 +30,5 @@ function coverage () { } module.exports = { - coverage: coverage + coverage } diff --git a/scripts/init.js b/scripts/init.js index 09cbee6b..10c51f5e 100644 --- a/scripts/init.js +++ b/scripts/init.js @@ -18,9 +18,9 @@ const { setDbEnvVars } = require('./util') function init () { const options = { env: { - 'NODE_ENV': 'production', - 'VIEWER_PORT': '8008', - 'PATH': process.env.PATH + NODE_ENV: 'production', + VIEWER_PORT: '8008', + PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] } @@ -31,5 +31,5 @@ function init () { } module.exports = { - init: init + init } diff --git a/scripts/preuninstall.js b/scripts/preuninstall.js index b22fd9e2..622f7a52 100644 --- a/scripts/preuninstall.js +++ b/scripts/preuninstall.js @@ -33,5 +33,5 @@ function preuninstall () { } module.exports = { - preuninstall: preuninstall + preuninstall } diff --git a/scripts/start-dev.js b/scripts/start-dev.js index 996af7f9..77e59580 100644 --- a/scripts/start-dev.js +++ b/scripts/start-dev.js @@ -19,7 +19,7 @@ const { setDbEnvVars } = require('./util') function startDev () { // Load .env file if it exists const envPath = path.resolve(process.cwd(), '.env') - let envVars = {} + const envVars = {} if (fs.existsSync(envPath)) { const envContent = fs.readFileSync(envPath, 'utf8') @@ -32,15 +32,14 @@ function startDev () { } } }) - } else { } // Create a new environment object with all variables const newEnv = { ...process.env, // Include existing environment variables ...envVars, // Override with .env variables - 'NODE_ENV': 'development', - 'PATH': process.env.PATH + NODE_ENV: 'development', + PATH: process.env.PATH } // Apply database environment variables @@ -53,5 +52,5 @@ function startDev () { } module.exports = { - startDev: startDev + startDev } diff --git a/scripts/start.js b/scripts/start.js index bf021f27..e3db44a7 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -18,9 +18,9 @@ const { setDbEnvVars } = require('./util') function start () { const options = { env: { - 'NODE_ENV': 'production', - 'VIEWER_PORT': '8008', - 'PATH': process.env.PATH + NODE_ENV: 'production', + VIEWER_PORT: '8008', + PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] } @@ -35,5 +35,5 @@ function start () { } module.exports = { - start: start + start } diff --git a/scripts/stop.js b/scripts/stop.js index bcbc4e8d..18c7b3a1 100644 --- a/scripts/stop.js +++ b/scripts/stop.js @@ -18,9 +18,9 @@ const { setDbEnvVars } = require('./util') function stop () { const options = { env: { - 'NODE_ENV': 'production', - 'VIEWER_PORT': '8008', - 'PATH': process.env.PATH + NODE_ENV: 'production', + VIEWER_PORT: '8008', + PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] } @@ -31,5 +31,5 @@ function stop () { } module.exports = { - stop: stop + stop } diff --git a/scripts/test.js b/scripts/test.js index c394b30d..f4a7e75c 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -18,9 +18,9 @@ const { setDbEnvVars } = require('./util') function test (useReporter, extraArgs) { const options = { env: { - 'NODE_ENV': 'test', - 'VIEWER_PORT': '8008', - 'PATH': process.env.PATH + NODE_ENV: 'test', + VIEWER_PORT: '8008', + PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] } @@ -39,5 +39,5 @@ function test (useReporter, extraArgs) { } module.exports = { - test: test + test } diff --git a/scripts/util.js b/scripts/util.js index 0245dd7d..f6cef3e5 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -13,7 +13,8 @@ const os = require('os') const fs = require('fs') -const ROOT_DIR = `${__dirname}/..` +const path = require('path') +const ROOT_DIR = path.join(__dirname, '..') const TEMP_DIR = getTempDir() const DEV_DB = `${ROOT_DIR}/src/data/sqlite_files/dev_database.sqlite` @@ -104,14 +105,14 @@ function setDbEnvVars (env) { } module.exports = { - backupDBs: backupDBs, - restoreDBs: restoreDBs, - backupConfigs: backupConfigs, - restoreConfigs: restoreConfigs, - renameFile: renameFile, - getTempDir: getTempDir, - setDbEnvVars: setDbEnvVars, - - TEMP_DIR: TEMP_DIR, - INSTALLATION_VARIABLES_FILE: INSTALLATION_VARIABLES_FILE + backupDBs, + restoreDBs, + backupConfigs, + restoreConfigs, + renameFile, + getTempDir, + setDbEnvVars, + + TEMP_DIR, + INSTALLATION_VARIABLES_FILE } diff --git a/src/cli/base-cli-handler.js b/src/cli/base-cli-handler.js index 48b0bdb3..da2adb3a 100644 --- a/src/cli/base-cli-handler.js +++ b/src/cli/base-cli-handler.js @@ -184,7 +184,7 @@ function argsArrayAsMap (args) { const argsMap = new Map() argsVars .map((pair) => pair.trim()) - .map((pair) => { + .forEach((pair) => { const spaceIndex = pair.indexOf(' ') let key; let values if (spaceIndex !== -1) { @@ -194,8 +194,8 @@ function argsArrayAsMap (args) { } else { key = pair values = [] + argsMap.set(key, values) } - argsMap.set(key, values) }) return argsMap } diff --git a/src/cli/cli-data-types.js b/src/cli/cli-data-types.js index 909e3c11..fa1aabd4 100644 --- a/src/cli/cli-data-types.js +++ b/src/cli/cli-data-types.js @@ -20,6 +20,6 @@ function Float (value) { } module.exports = { - Integer: Integer, - Float: Float + Integer, + Float } diff --git a/src/cli/config.js b/src/cli/config.js index 6492c4b0..e6c7bcdd 100644 --- a/src/cli/config.js +++ b/src/cli/config.js @@ -215,7 +215,7 @@ const updateConfig = async function (newConfigValue, cliConfigName, configName, const _listConfigOptions = function () { const configuration = { - 'Port': config.get('server.port'), + Port: config.get('server.port'), 'SSL key directory': config.get('server.ssl.path.key'), 'SSL certificate directory': config.get('server.ssl.path.cert'), 'Intermediate key directory': config.get('server.ssl.path.intermediateCert'), diff --git a/src/cli/iofog.js b/src/cli/iofog.js index 0bff3db5..ece5a2e8 100644 --- a/src/cli/iofog.js +++ b/src/cli/iofog.js @@ -439,7 +439,7 @@ async function _getHalHardwareInfo (obj) { logger.cliReq('fog hal-hw', { args: uuidObj }) const data = await FogService.getHalHardwareInfoEndPoint(uuidObj, {}, true) if (data) { - if (data.hasOwnProperty('info')) { + if (Object.hasOwn(data, 'info')) { data.info = JSON.parse(data.info) } @@ -454,7 +454,7 @@ async function _getHalUsbInfo (obj) { logger.cliReq('fog hal-usb', { args: uuidObj }) const data = await FogService.getHalUsbInfoEndPoint(uuidObj, {}, true) if (data) { - if (data.hasOwnProperty('info')) { + if (Object.hasOwn(data, 'info')) { data.info = JSON.parse(data.info) } diff --git a/src/cli/microservice.js b/src/cli/microservice.js index d4cde52b..a82d42e1 100644 --- a/src/cli/microservice.js +++ b/src/cli/microservice.js @@ -582,17 +582,17 @@ const _updateMicroservice = async function (obj) { const _updateMicroserviceObject = function (obj) { const envVars = obj.env || [] - const env = envVars.map((it) => { + const env = envVars.flatMap((it) => { const split = it.split('=') if (!split || split.length < 2) { - return + return [] } - return { + return [{ key: split[0], value: split.slice(1).join('=') - } - }).filter((it) => !!it) + }] + }) const microserviceObj = { name: obj.name, @@ -648,17 +648,17 @@ const _updateMicroserviceObject = function (obj) { const _createMicroserviceObject = function (obj) { const envVars = obj.env || [] - const env = envVars.map((it) => { + const env = envVars.flatMap((it) => { const split = it.split('=') if (!split || split.length < 2) { - return + return [] } - return { + return [{ key: split[0], value: split.slice(1).join('=') - } - }).filter((it) => !!it) + }] + }) const microserviceObj = { name: obj.name, diff --git a/src/cli/start.js b/src/cli/start.js index 0b169502..68649332 100644 --- a/src/cli/start.js +++ b/src/cli/start.js @@ -36,6 +36,7 @@ class Start extends BaseCLIHandler { } ] } + async run (args) { const startCommand = this.parseCommandLineArgs(this.commandDefinitions, { argv: args.argv, partial: false }) const daemon = args.daemon diff --git a/src/config/env-mapping.js b/src/config/env-mapping.js index fa54d872..d9fa2dbf 100644 --- a/src/config/env-mapping.js +++ b/src/config/env-mapping.js @@ -1,159 +1,159 @@ module.exports = { // Application Configuration - 'CONTROLLER_NAME': 'app.name', - 'CONTROLLER_UUID': 'app.uuid', - 'CONTROL_PLANE': 'app.controlPlane', - 'CONTROLLER_NAMESPACE': 'app.namespace', + CONTROLLER_NAME: 'app.name', + CONTROLLER_UUID: 'app.uuid', + CONTROL_PLANE: 'app.controlPlane', + CONTROLLER_NAMESPACE: 'app.namespace', // Server Configuration - 'SERVER_PORT': 'server.port', - 'SERVER_DEV_MODE': 'server.devMode', - 'CONTROLLER_PUBLIC_URL': 'server.publicUrl', - 'TRUST_PROXY': 'server.trustProxy', + SERVER_PORT: 'server.port', + SERVER_DEV_MODE: 'server.devMode', + CONTROLLER_PUBLIC_URL: 'server.publicUrl', + TRUST_PROXY: 'server.trustProxy', - 'WS_PING_INTERVAL': 'server.webSocket.pingInterval', - 'WS_PONG_TIMEOUT': 'server.webSocket.pongTimeout', - 'WS_MAX_PAYLOAD': 'server.webSocket.maxPayload', - 'WS_SESSION_TIMEOUT': 'server.webSocket.session.timeout', - 'WS_SESSION_MAX_CONNECTIONS': 'server.webSocket.session.maxConnections', - 'WS_CLEANUP_INTERVAL': 'server.webSocket.session.cleanupInterval', - 'WS_SECURITY_MAX_CONNECTIONS_PER_IP': 'server.webSocket.security.maxConnectionsPerIp', - 'WS_SECURITY_MAX_REQUESTS_PER_MINUTE': 'server.webSocket.security.maxRequestsPerMinute', - 'WS_SECURITY_MAX_PAYLOAD': 'server.webSocket.security.maxPayload', + WS_PING_INTERVAL: 'server.webSocket.pingInterval', + WS_PONG_TIMEOUT: 'server.webSocket.pongTimeout', + WS_MAX_PAYLOAD: 'server.webSocket.maxPayload', + WS_SESSION_TIMEOUT: 'server.webSocket.session.timeout', + WS_SESSION_MAX_CONNECTIONS: 'server.webSocket.session.maxConnections', + WS_CLEANUP_INTERVAL: 'server.webSocket.session.cleanupInterval', + WS_SECURITY_MAX_CONNECTIONS_PER_IP: 'server.webSocket.security.maxConnectionsPerIp', + WS_SECURITY_MAX_REQUESTS_PER_MINUTE: 'server.webSocket.security.maxRequestsPerMinute', + WS_SECURITY_MAX_PAYLOAD: 'server.webSocket.security.maxPayload', // TLS Configuration (listener certificates; env TLS_* per Plan 8.1) - 'TLS_PATH_KEY': 'server.tls.path.key', - 'TLS_PATH_CERT': 'server.tls.path.cert', - 'TLS_PATH_INTERMEDIATE_CERT': 'server.tls.path.intermediateCert', - 'TLS_BASE64_KEY': 'server.tls.base64.key', - 'TLS_BASE64_CERT': 'server.tls.base64.cert', - 'TLS_BASE64_INTERMEDIATE_CERT': 'server.tls.base64.intermediateCert', + TLS_PATH_KEY: 'server.tls.path.key', + TLS_PATH_CERT: 'server.tls.path.cert', + TLS_PATH_INTERMEDIATE_CERT: 'server.tls.path.intermediateCert', + TLS_BASE64_KEY: 'server.tls.base64.key', + TLS_BASE64_CERT: 'server.tls.base64.cert', + TLS_BASE64_INTERMEDIATE_CERT: 'server.tls.base64.intermediateCert', // Viewer Configuration - 'VIEWER_PORT': 'viewer.port', - 'VIEWER_URL': 'viewer.url', + VIEWER_PORT: 'viewer.port', + VIEWER_URL: 'viewer.url', // Logging Configuration - 'LOG_LEVEL': 'log.level', - 'LOG_DIRECTORY': 'log.directory', - 'LOG_FILE_SIZE': 'log.fileSize', - 'LOG_FILE_COUNT': 'log.fileCount', + LOG_LEVEL: 'log.level', + LOG_DIRECTORY: 'log.directory', + LOG_FILE_SIZE: 'log.fileSize', + LOG_FILE_COUNT: 'log.fileCount', // Settings Configuration - 'FOG_STATUS_UPDATE_INTERVAL': 'settings.fogStatusUpdateInterval', - 'FOG_STATUS_UPDATE_TOLERANCE': 'settings.fogStatusUpdateTolerance', - 'FOG_EXPIRED_TOKEN_CLEANUP_INTERVAL': 'settings.fogExpiredTokenCleanupInterval', - 'EVENT_RETENTION_DAYS': 'settings.eventRetentionDays', - 'EVENT_CLEANUP_INTERVAL': 'settings.eventCleanupInterval', - 'EVENT_AUDIT_ENABLED': 'settings.eventAuditEnabled', - 'EVENT_CAPTURE_IP_ADDRESS': 'settings.eventCaptureIpAddress', - 'CONTROLLER_HEARTBEAT_INTERVAL': 'settings.controllerHeartbeatInterval', - 'CONTROLLER_INACTIVE_THRESHOLD': 'settings.controllerInactiveThreshold', - 'CONTROLLER_CLEANUP_INTERVAL': 'settings.controllerCleanupInterval', + FOG_STATUS_UPDATE_INTERVAL: 'settings.fogStatusUpdateInterval', + FOG_STATUS_UPDATE_TOLERANCE: 'settings.fogStatusUpdateTolerance', + FOG_EXPIRED_TOKEN_CLEANUP_INTERVAL: 'settings.fogExpiredTokenCleanupInterval', + EVENT_RETENTION_DAYS: 'settings.eventRetentionDays', + EVENT_CLEANUP_INTERVAL: 'settings.eventCleanupInterval', + EVENT_AUDIT_ENABLED: 'settings.eventAuditEnabled', + EVENT_CAPTURE_IP_ADDRESS: 'settings.eventCaptureIpAddress', + CONTROLLER_HEARTBEAT_INTERVAL: 'settings.controllerHeartbeatInterval', + CONTROLLER_INACTIVE_THRESHOLD: 'settings.controllerInactiveThreshold', + CONTROLLER_CLEANUP_INTERVAL: 'settings.controllerCleanupInterval', // Database Configuration - 'DB_PROVIDER': 'database.provider', + DB_PROVIDER: 'database.provider', // These will map to the appropriate provider based on DB_PROVIDER - 'DB_HOST': { + DB_HOST: { path: (provider) => `database.${provider}.host` }, - 'DB_PORT': { + DB_PORT: { path: (provider) => `database.${provider}.port` }, - 'DB_USERNAME': { + DB_USERNAME: { path: (provider) => `database.${provider}.username` }, - 'DB_PASSWORD': { + DB_PASSWORD: { path: (provider) => `database.${provider}.password` }, - 'DB_NAME': { + DB_NAME: { path: (provider) => `database.${provider}.databaseName` }, - 'DB_USE_SSL': { + DB_USE_SSL: { path: (provider) => `database.${provider}.useSSL` }, - 'DB_SSL_CA': { + DB_SSL_CA: { path: (provider) => `database.${provider}.sslCA` }, // Auth Configuration (OIDC — k8s-style; naming-map §13) - 'AUTH_MODE': 'auth.mode', - 'OIDC_ISSUER_URL': 'auth.issuerUrl', - 'OIDC_CLIENT_ID': 'auth.client.id', - 'OIDC_CLIENT_SECRET': 'auth.client.secret', - 'OIDC_VIEWER_CLIENT_ID': 'auth.viewerClient', - 'AUTH_VIEWER_CLIENT_ENABLED': 'auth.viewerClient.enabled', - 'AUTH_INSECURE_ALLOW_HTTP': 'auth.insecureAllowHttp', - 'OIDC_BOOTSTRAP_ADMIN_USERNAME': 'auth.bootstrap.adminUsername', - 'OIDC_BOOTSTRAP_ADMIN_PASSWORD': 'auth.bootstrap.adminPassword', - 'AUTH_INSECURE_ALLOW_BOOTSTRAP_LOG': 'auth.insecureAllowBootstrapLog', - 'AUTH_RATE_LIMIT_ENABLED': 'auth.rateLimit.enabled', - 'AUTH_RATE_LIMIT_MAX_REQUESTS': 'auth.rateLimit.maxRequestsPerWindow', - 'AUTH_RATE_LIMIT_WINDOW_MS': 'auth.rateLimit.windowMs', - 'AUTH_SESSION_STORE_TYPE': 'auth.sessionStore.type', - 'AUTH_SESSION_STORE_TTL_MS': 'auth.sessionStore.ttlMs', - 'AUTH_SESSION_SECRET': 'auth.sessionStore.secret', - 'AUTH_OIDC_INTERACTION_TTL_SECONDS': 'auth.oidcTtl.interactionTtlSeconds', - 'AUTH_OIDC_GRANT_TTL_SECONDS': 'auth.oidcTtl.grantTtlSeconds', - 'AUTH_OIDC_SESSION_TTL_SECONDS': 'auth.oidcTtl.sessionTtlSeconds', - 'AUTH_OIDC_ID_TOKEN_TTL_SECONDS': 'auth.oidcTtl.idTokenTtlSeconds', - 'AUTH_ACCESS_TOKEN_TTL_SECONDS': 'auth.tokenTtl.accessTokenTtlSeconds', - 'AUTH_REFRESH_TOKEN_TTL_SECONDS': 'auth.tokenTtl.refreshTokenTtlSeconds', + AUTH_MODE: 'auth.mode', + OIDC_ISSUER_URL: 'auth.issuerUrl', + OIDC_CLIENT_ID: 'auth.client.id', + OIDC_CLIENT_SECRET: 'auth.client.secret', + OIDC_VIEWER_CLIENT_ID: 'auth.viewerClient', + AUTH_VIEWER_CLIENT_ENABLED: 'auth.viewerClient.enabled', + AUTH_INSECURE_ALLOW_HTTP: 'auth.insecureAllowHttp', + OIDC_BOOTSTRAP_ADMIN_USERNAME: 'auth.bootstrap.adminUsername', + OIDC_BOOTSTRAP_ADMIN_PASSWORD: 'auth.bootstrap.adminPassword', + AUTH_INSECURE_ALLOW_BOOTSTRAP_LOG: 'auth.insecureAllowBootstrapLog', + AUTH_RATE_LIMIT_ENABLED: 'auth.rateLimit.enabled', + AUTH_RATE_LIMIT_MAX_REQUESTS: 'auth.rateLimit.maxRequestsPerWindow', + AUTH_RATE_LIMIT_WINDOW_MS: 'auth.rateLimit.windowMs', + AUTH_SESSION_STORE_TYPE: 'auth.sessionStore.type', + AUTH_SESSION_STORE_TTL_MS: 'auth.sessionStore.ttlMs', + AUTH_SESSION_SECRET: 'auth.sessionStore.secret', + AUTH_OIDC_INTERACTION_TTL_SECONDS: 'auth.oidcTtl.interactionTtlSeconds', + AUTH_OIDC_GRANT_TTL_SECONDS: 'auth.oidcTtl.grantTtlSeconds', + AUTH_OIDC_SESSION_TTL_SECONDS: 'auth.oidcTtl.sessionTtlSeconds', + AUTH_OIDC_ID_TOKEN_TTL_SECONDS: 'auth.oidcTtl.idTokenTtlSeconds', + AUTH_ACCESS_TOKEN_TTL_SECONDS: 'auth.tokenTtl.accessTokenTtlSeconds', + AUTH_REFRESH_TOKEN_TTL_SECONDS: 'auth.tokenTtl.refreshTokenTtlSeconds', // Bridge Ports Configuration - 'BRIDGE_PORTS_RANGE': 'bridgePorts.range', + BRIDGE_PORTS_RANGE: 'bridgePorts.range', // System Images Configuration - 'ROUTER_IMAGE_1': 'systemImages.router.1', - 'ROUTER_IMAGE_2': 'systemImages.router.2', - 'ROUTER_IMAGE_3': 'systemImages.router.3', - 'ROUTER_IMAGE_4': 'systemImages.router.4', - 'DEBUG_IMAGE_1': 'systemImages.debug.1', - 'DEBUG_IMAGE_2': 'systemImages.debug.2', - 'DEBUG_IMAGE_3': 'systemImages.debug.3', - 'DEBUG_IMAGE_4': 'systemImages.debug.4', - 'NATS_IMAGE_1': 'systemImages.nats.1', - 'NATS_IMAGE_2': 'systemImages.nats.2', - 'NATS_IMAGE_3': 'systemImages.nats.3', - 'NATS_IMAGE_4': 'systemImages.nats.4', + ROUTER_IMAGE_1: 'systemImages.router.1', + ROUTER_IMAGE_2: 'systemImages.router.2', + ROUTER_IMAGE_3: 'systemImages.router.3', + ROUTER_IMAGE_4: 'systemImages.router.4', + DEBUG_IMAGE_1: 'systemImages.debug.1', + DEBUG_IMAGE_2: 'systemImages.debug.2', + DEBUG_IMAGE_3: 'systemImages.debug.3', + DEBUG_IMAGE_4: 'systemImages.debug.4', + NATS_IMAGE_1: 'systemImages.nats.1', + NATS_IMAGE_2: 'systemImages.nats.2', + NATS_IMAGE_3: 'systemImages.nats.3', + NATS_IMAGE_4: 'systemImages.nats.4', // NATS Configuration - 'NATS_ENABLED': 'nats.enabled', + NATS_ENABLED: 'nats.enabled', // Vault Configuration - 'VAULT_ENABLED': 'vault.enabled', - 'VAULT_PROVIDER': 'vault.provider', - 'VAULT_BASE_PATH': 'vault.basePath', + VAULT_ENABLED: 'vault.enabled', + VAULT_PROVIDER: 'vault.provider', + VAULT_BASE_PATH: 'vault.basePath', // HashiCorp Vault - 'VAULT_HASHICORP_ADDRESS': 'vault.hashicorp.address', - 'VAULT_HASHICORP_TOKEN': 'vault.hashicorp.token', - 'VAULT_HASHICORP_MOUNT': 'vault.hashicorp.mount', + VAULT_HASHICORP_ADDRESS: 'vault.hashicorp.address', + VAULT_HASHICORP_TOKEN: 'vault.hashicorp.token', + VAULT_HASHICORP_MOUNT: 'vault.hashicorp.mount', // AWS Secrets Manager - 'VAULT_AWS_REGION': 'vault.aws.region', - 'VAULT_AWS_ACCESS_KEY_ID': 'vault.aws.accessKeyId', - 'VAULT_AWS_ACCESS_KEY': 'vault.aws.accessKey', + VAULT_AWS_REGION: 'vault.aws.region', + VAULT_AWS_ACCESS_KEY_ID: 'vault.aws.accessKeyId', + VAULT_AWS_ACCESS_KEY: 'vault.aws.accessKey', // Azure Key Vault - 'VAULT_AZURE_URL': 'vault.azure.url', - 'VAULT_AZURE_TENANT_ID': 'vault.azure.tenantId', - 'VAULT_AZURE_CLIENT_ID': 'vault.azure.clientId', - 'VAULT_AZURE_CLIENT_SECRET': 'vault.azure.clientSecret', + VAULT_AZURE_URL: 'vault.azure.url', + VAULT_AZURE_TENANT_ID: 'vault.azure.tenantId', + VAULT_AZURE_CLIENT_ID: 'vault.azure.clientId', + VAULT_AZURE_CLIENT_SECRET: 'vault.azure.clientSecret', // Google Secret Manager - 'VAULT_GOOGLE_PROJECT_ID': 'vault.google.projectId', - 'VAULT_GOOGLE_CREDENTIALS': 'vault.google.credentials', + VAULT_GOOGLE_PROJECT_ID: 'vault.google.projectId', + VAULT_GOOGLE_CREDENTIALS: 'vault.google.credentials', // OpenTelemetry Configuration - 'ENABLE_TELEMETRY': 'otel.enabled', - 'OTEL_SERVICE_NAME': 'otel.serviceName', - 'OTEL_EXPORTER_OTLP_ENDPOINT': 'otel.endpoint', - 'OTEL_EXPORTER_OTLP_PROTOCOL': 'otel.protocol', - 'OTEL_EXPORTER_OTLP_HEADERS': 'otel.headers', - 'OTEL_RESOURCE_ATTRIBUTES': 'otel.resourceAttributes', - 'OTEL_METRICS_EXPORTER': 'otel.metrics.exporter', - 'OTEL_METRICS_INTERVAL': 'otel.metrics.interval', - 'OTEL_LOG_LEVEL': 'otel.logs.level', - 'OTEL_PROPAGATORS': 'otel.propagators', - 'OTEL_TRACES_SAMPLER': 'otel.traces.sampler', - 'OTEL_TRACES_SAMPLER_ARG': 'otel.traces.samplerArg', - 'OTEL_BATCH_SIZE': 'otel.batch.size', - 'OTEL_BATCH_DELAY': 'otel.batch.delay' + ENABLE_TELEMETRY: 'otel.enabled', + OTEL_SERVICE_NAME: 'otel.serviceName', + OTEL_EXPORTER_OTLP_ENDPOINT: 'otel.endpoint', + OTEL_EXPORTER_OTLP_PROTOCOL: 'otel.protocol', + OTEL_EXPORTER_OTLP_HEADERS: 'otel.headers', + OTEL_RESOURCE_ATTRIBUTES: 'otel.resourceAttributes', + OTEL_METRICS_EXPORTER: 'otel.metrics.exporter', + OTEL_METRICS_INTERVAL: 'otel.metrics.interval', + OTEL_LOG_LEVEL: 'otel.logs.level', + OTEL_PROPAGATORS: 'otel.propagators', + OTEL_TRACES_SAMPLER: 'otel.traces.sampler', + OTEL_TRACES_SAMPLER_ARG: 'otel.traces.samplerArg', + OTEL_BATCH_SIZE: 'otel.batch.size', + OTEL_BATCH_DELAY: 'otel.batch.delay' } diff --git a/src/config/index.js b/src/config/index.js index 267697a4..a2325273 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -145,7 +145,7 @@ class Config { get (key, defaultValue) { // Replace dots with colons for nconf compatibility const nconfKey = key.replace(/\./g, ':') - let value = nconf.get(nconfKey) + const value = nconf.get(nconfKey) return value !== undefined ? value : defaultValue } diff --git a/src/controllers/agent-controller.js b/src/controllers/agent-controller.js index 2584ef1b..fa9b199c 100644 --- a/src/controllers/agent-controller.js +++ b/src/controllers/agent-controller.js @@ -112,7 +112,7 @@ const registerControllerMicroserviceEndPoint = async function (req, fog) { } module.exports = { - agentProvisionEndPoint: agentProvisionEndPoint, + agentProvisionEndPoint, agentDeprovisionEndPoint: AuthDecorator.checkFogToken(agentDeprovisionEndPoint), getAgentConfigEndPoint: AuthDecorator.checkFogToken(getAgentConfigEndPoint), updateAgentConfigEndPoint: AuthDecorator.checkFogToken(updateAgentConfigEndPoint), diff --git a/src/controllers/application-controller.js b/src/controllers/application-controller.js index dc362314..89597b46 100644 --- a/src/controllers/application-controller.js +++ b/src/controllers/application-controller.js @@ -116,18 +116,18 @@ const getApplicationByIdEndPoint = async function (req) { } module.exports = { - createApplicationEndPoint: (createApplicationEndPoint), - createApplicationYAMLEndPoint: (createApplicationYAMLEndPoint), - getApplicationsByUserEndPoint: (getApplicationsByUserEndPoint), - getApplicationsBySystemEndPoint: (getApplicationsBySystemEndPoint), - getApplicationEndPoint: (getApplicationEndPoint), - getSystemApplicationEndPoint: (getSystemApplicationEndPoint), - getApplicationByIdEndPoint: (getApplicationByIdEndPoint), - updateApplicationEndPoint: (updateApplicationEndPoint), - updateApplicationYAMLEndPoint: (updateApplicationYAMLEndPoint), - patchApplicationEndPoint: (patchApplicationEndPoint), - patchApplicationByIdEndPoint: (patchApplicationByIdEndPoint), - deleteApplicationEndPoint: (deleteApplicationEndPoint), - deleteSystemApplicationEndPoint: (deleteSystemApplicationEndPoint), - deleteApplicationByIdEndPoint: (deleteApplicationByIdEndPoint) + createApplicationEndPoint, + createApplicationYAMLEndPoint, + getApplicationsByUserEndPoint, + getApplicationsBySystemEndPoint, + getApplicationEndPoint, + getSystemApplicationEndPoint, + getApplicationByIdEndPoint, + updateApplicationEndPoint, + updateApplicationYAMLEndPoint, + patchApplicationEndPoint, + patchApplicationByIdEndPoint, + deleteApplicationEndPoint, + deleteSystemApplicationEndPoint, + deleteApplicationByIdEndPoint } diff --git a/src/controllers/application-template-controller.js b/src/controllers/application-template-controller.js index 61f28111..b7e8f192 100644 --- a/src/controllers/application-template-controller.js +++ b/src/controllers/application-template-controller.js @@ -77,12 +77,12 @@ const deleteApplicationTemplateEndPoint = async function (req) { } module.exports = { - createApplicationTemplateEndPoint: (createApplicationTemplateEndPoint), - getApplicationTemplatesByUserEndPoint: (getApplicationTemplatesByUserEndPoint), - getApplicationTemplateEndPoint: (getApplicationTemplateEndPoint), - updateApplicationTemplateEndPoint: (updateApplicationTemplateEndPoint), - updateApplicationTemplateYAMLEndPoint: (updateApplicationTemplateYAMLEndPoint), - patchApplicationTemplateEndPoint: (patchApplicationTemplateEndPoint), - deleteApplicationTemplateEndPoint: (deleteApplicationTemplateEndPoint), - createApplicationTemplateYAMLEndPoint: (createApplicationTemplateYAMLEndPoint) + createApplicationTemplateEndPoint, + getApplicationTemplatesByUserEndPoint, + getApplicationTemplateEndPoint, + updateApplicationTemplateEndPoint, + updateApplicationTemplateYAMLEndPoint, + patchApplicationTemplateEndPoint, + deleteApplicationTemplateEndPoint, + createApplicationTemplateYAMLEndPoint } diff --git a/src/controllers/catalog-controller.js b/src/controllers/catalog-controller.js index dcd35cd5..d04e7451 100644 --- a/src/controllers/catalog-controller.js +++ b/src/controllers/catalog-controller.js @@ -34,9 +34,9 @@ const updateCatalogItemEndPoint = async function (req) { } module.exports = { - createCatalogItemEndPoint: (createCatalogItemEndPoint), - listCatalogItemsEndPoint: (listCatalogItemsEndPoint), - listCatalogItemEndPoint: (listCatalogItemEndPoint), - deleteCatalogItemEndPoint: (deleteCatalogItemEndPoint), - updateCatalogItemEndPoint: (updateCatalogItemEndPoint) + createCatalogItemEndPoint, + listCatalogItemsEndPoint, + listCatalogItemEndPoint, + deleteCatalogItemEndPoint, + updateCatalogItemEndPoint } diff --git a/src/controllers/config-controller.js b/src/controllers/config-controller.js index 2b5efd72..29892201 100644 --- a/src/controllers/config-controller.js +++ b/src/controllers/config-controller.js @@ -28,7 +28,7 @@ const getConfigEndpoint = async function (req) { } module.exports = { - upsertConfigElementEndpoint: (upsertConfigElementEndpoint), - listConfigEndpoint: (listConfigEndpoint), - getConfigEndpoint: (getConfigEndpoint) + upsertConfigElementEndpoint, + listConfigEndpoint, + getConfigEndpoint } diff --git a/src/controllers/config-map-controller.js b/src/controllers/config-map-controller.js index ec79358e..3a0a4e63 100644 --- a/src/controllers/config-map-controller.js +++ b/src/controllers/config-map-controller.js @@ -50,7 +50,7 @@ const updateConfigMapFromYamlEndpoint = async function (req) { const configMapName = req.params.name const configMapData = await YamlParserService.parseConfigMapFile(fileContent, { isUpdate: true, - configMapName: configMapName + configMapName }) return ConfigMapService.updateConfigMapEndpoint(configMapName, configMapData) } diff --git a/src/controllers/controller.js b/src/controllers/controller.js index 35da1ca1..48390539 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -22,6 +22,6 @@ const architecturesEndPoint = async function (req) { } module.exports = { - statusControllerEndPoint: statusControllerEndPoint, - architecturesEndPoint: architecturesEndPoint + statusControllerEndPoint, + architecturesEndPoint } diff --git a/src/controllers/iofog-controller.js b/src/controllers/iofog-controller.js index a89950f2..c24de528 100644 --- a/src/controllers/iofog-controller.js +++ b/src/controllers/iofog-controller.js @@ -112,17 +112,17 @@ async function disableNodeExecEndPoint (req) { } module.exports = { - createFogEndPoint: (createFogEndPoint), - updateFogEndPoint: (updateFogEndPoint), - deleteFogEndPoint: (deleteFogEndPoint), - getFogEndPoint: (getFogEndPoint), - getFogListEndPoint: (getFogListEndPoint), + createFogEndPoint, + updateFogEndPoint, + deleteFogEndPoint, + getFogEndPoint, + getFogListEndPoint, generateProvisioningKeyEndPoint: (generateProvisionKeyEndPoint), - setFogVersionCommandEndPoint: (setFogVersionCommandEndPoint), - setFogRebootCommandEndPoint: (setFogRebootCommandEndPoint), - getHalHardwareInfoEndPoint: (getHalHardwareInfoEndPoint), - getHalUsbInfoEndPoint: (getHalUsbInfoEndPoint), - setFogPruneCommandEndPoint: (setFogPruneCommandEndPoint), - enableNodeExecEndPoint: (enableNodeExecEndPoint), - disableNodeExecEndPoint: (disableNodeExecEndPoint) + setFogVersionCommandEndPoint, + setFogRebootCommandEndPoint, + getHalHardwareInfoEndPoint, + getHalUsbInfoEndPoint, + setFogPruneCommandEndPoint, + enableNodeExecEndPoint, + disableNodeExecEndPoint } diff --git a/src/controllers/microservices-controller.js b/src/controllers/microservices-controller.js index 9d62153c..781326ed 100644 --- a/src/controllers/microservices-controller.js +++ b/src/controllers/microservices-controller.js @@ -151,7 +151,7 @@ const listMicroservicePortMappingsEndPoint = async function (req) { const uuid = req.params.uuid const ports = await MicroservicesService.listMicroservicePortMappingsEndPoint(uuid, false) return { - ports: ports + ports } } @@ -177,7 +177,7 @@ const listMicroserviceVolumeMappingsEndPoint = async function (req) { const uuid = req.params.uuid const volumeMappings = await MicroservicesService.listVolumeMappingsEndPoint(uuid, false) return { - volumeMappings: volumeMappings + volumeMappings } } @@ -224,39 +224,39 @@ const stopMicroserviceEndPoint = async function (req) { } module.exports = { - createMicroserviceOnFogEndPoint: (createMicroserviceOnFogEndPoint), - getMicroserviceEndPoint: (getMicroserviceEndPoint), - getSystemMicroserviceEndPoint: (getSystemMicroserviceEndPoint), - updateMicroserviceEndPoint: (updateMicroserviceEndPoint), - updateSystemMicroserviceEndPoint: (updateSystemMicroserviceEndPoint), - rebuildMicroserviceEndPoint: (rebuildMicroserviceEndPoint), - rebuildSystemMicroserviceEndPoint: (rebuildSystemMicroserviceEndPoint), - deleteMicroserviceEndPoint: (deleteMicroserviceEndPoint), - getMicroservicesByApplicationEndPoint: (getMicroservicesByApplicationEndPoint), - getSystemMicroservicesByApplicationEndPoint: (getSystemMicroservicesByApplicationEndPoint), - createMicroservicePortMappingEndPoint: (createMicroservicePortMappingEndPoint), - createSystemMicroservicePortMappingEndPoint: (createSystemMicroservicePortMappingEndPoint), - deleteMicroservicePortMappingEndPoint: (deleteMicroservicePortMappingEndPoint), - deleteSystemMicroservicePortMappingEndPoint: (deleteSystemMicroservicePortMappingEndPoint), + createMicroserviceOnFogEndPoint, + getMicroserviceEndPoint, + getSystemMicroserviceEndPoint, + updateMicroserviceEndPoint, + updateSystemMicroserviceEndPoint, + rebuildMicroserviceEndPoint, + rebuildSystemMicroserviceEndPoint, + deleteMicroserviceEndPoint, + getMicroservicesByApplicationEndPoint, + getSystemMicroservicesByApplicationEndPoint, + createMicroservicePortMappingEndPoint, + createSystemMicroservicePortMappingEndPoint, + deleteMicroservicePortMappingEndPoint, + deleteSystemMicroservicePortMappingEndPoint, getMicroservicePortMappingListEndPoint: (listMicroservicePortMappingsEndPoint), - createMicroserviceVolumeMappingEndPoint: (createMicroserviceVolumeMappingEndPoint), - createSystemMicroserviceVolumeMappingEndPoint: (createSystemMicroserviceVolumeMappingEndPoint), - listMicroserviceVolumeMappingsEndPoint: (listMicroserviceVolumeMappingsEndPoint), - deleteMicroserviceVolumeMappingEndPoint: (deleteMicroserviceVolumeMappingEndPoint), - deleteSystemMicroserviceVolumeMappingEndPoint: (deleteSystemMicroserviceVolumeMappingEndPoint), - createMicroserviceYAMLEndPoint: (createMicroserviceYAMLEndPoint), - updateMicroserviceYAMLEndPoint: (updateMicroserviceYAMLEndPoint), - updateSystemMicroserviceYAMLEndPoint: (updateSystemMicroserviceYAMLEndPoint), - updateMicroserviceConfigEndPoint: (updateMicroserviceConfigEndPoint), - getMicroserviceConfigEndPoint: (getMicroserviceConfigEndPoint), - updateSystemMicroserviceConfigEndPoint: (updateSystemMicroserviceConfigEndPoint), - getSystemMicroserviceConfigEndPoint: (getSystemMicroserviceConfigEndPoint), - deleteMicroserviceConfigEndPoint: (deleteMicroserviceConfigEndPoint), - deleteSystemMicroserviceConfigEndPoint: (deleteSystemMicroserviceConfigEndPoint), - createMicroserviceExecEndPoint: (createMicroserviceExecEndPoint), - deleteMicroserviceExecEndPoint: (deleteMicroserviceExecEndPoint), - createSystemMicroserviceExecEndPoint: (createSystemMicroserviceExecEndPoint), - deleteSystemMicroserviceExecEndPoint: (deleteSystemMicroserviceExecEndPoint), - startMicroserviceEndPoint: (startMicroserviceEndPoint), - stopMicroserviceEndPoint: (stopMicroserviceEndPoint) + createMicroserviceVolumeMappingEndPoint, + createSystemMicroserviceVolumeMappingEndPoint, + listMicroserviceVolumeMappingsEndPoint, + deleteMicroserviceVolumeMappingEndPoint, + deleteSystemMicroserviceVolumeMappingEndPoint, + createMicroserviceYAMLEndPoint, + updateMicroserviceYAMLEndPoint, + updateSystemMicroserviceYAMLEndPoint, + updateMicroserviceConfigEndPoint, + getMicroserviceConfigEndPoint, + updateSystemMicroserviceConfigEndPoint, + getSystemMicroserviceConfigEndPoint, + deleteMicroserviceConfigEndPoint, + deleteSystemMicroserviceConfigEndPoint, + createMicroserviceExecEndPoint, + deleteMicroserviceExecEndPoint, + createSystemMicroserviceExecEndPoint, + deleteSystemMicroserviceExecEndPoint, + startMicroserviceEndPoint, + stopMicroserviceEndPoint } diff --git a/src/controllers/rbac-controller.js b/src/controllers/rbac-controller.js index fbfe25b8..d10e76b7 100644 --- a/src/controllers/rbac-controller.js +++ b/src/controllers/rbac-controller.js @@ -139,27 +139,27 @@ const updateServiceAccountFromYamlEndpoint = async function (req) { module.exports = { // Role endpoints - listRolesEndpoint: (listRolesEndpoint), - getRoleEndpoint: (getRoleEndpoint), - createRoleEndpoint: (createRoleEndpoint), - updateRoleEndpoint: (updateRoleEndpoint), - deleteRoleEndpoint: (deleteRoleEndpoint), - createRoleFromYamlEndpoint: (createRoleFromYamlEndpoint), - updateRoleFromYamlEndpoint: (updateRoleFromYamlEndpoint), + listRolesEndpoint, + getRoleEndpoint, + createRoleEndpoint, + updateRoleEndpoint, + deleteRoleEndpoint, + createRoleFromYamlEndpoint, + updateRoleFromYamlEndpoint, // RoleBinding endpoints - listRoleBindingsEndpoint: (listRoleBindingsEndpoint), - getRoleBindingEndpoint: (getRoleBindingEndpoint), - createRoleBindingEndpoint: (createRoleBindingEndpoint), - updateRoleBindingEndpoint: (updateRoleBindingEndpoint), - deleteRoleBindingEndpoint: (deleteRoleBindingEndpoint), - createRoleBindingFromYamlEndpoint: (createRoleBindingFromYamlEndpoint), - updateRoleBindingFromYamlEndpoint: (updateRoleBindingFromYamlEndpoint), + listRoleBindingsEndpoint, + getRoleBindingEndpoint, + createRoleBindingEndpoint, + updateRoleBindingEndpoint, + deleteRoleBindingEndpoint, + createRoleBindingFromYamlEndpoint, + updateRoleBindingFromYamlEndpoint, // ServiceAccount endpoints - listServiceAccountsEndpoint: (listServiceAccountsEndpoint), - getServiceAccountEndpoint: (getServiceAccountEndpoint), - createServiceAccountEndpoint: (createServiceAccountEndpoint), - updateServiceAccountEndpoint: (updateServiceAccountEndpoint), - deleteServiceAccountEndpoint: (deleteServiceAccountEndpoint), - createServiceAccountFromYamlEndpoint: (createServiceAccountFromYamlEndpoint), - updateServiceAccountFromYamlEndpoint: (updateServiceAccountFromYamlEndpoint) + listServiceAccountsEndpoint, + getServiceAccountEndpoint, + createServiceAccountEndpoint, + updateServiceAccountEndpoint, + deleteServiceAccountEndpoint, + createServiceAccountFromYamlEndpoint, + updateServiceAccountFromYamlEndpoint } diff --git a/src/controllers/registry-controller.js b/src/controllers/registry-controller.js index 84b240e6..19292fd5 100644 --- a/src/controllers/registry-controller.js +++ b/src/controllers/registry-controller.js @@ -40,9 +40,9 @@ const updateRegistryEndPoint = async function (req) { } module.exports = { - createRegistryEndPoint: (createRegistryEndPoint), - getRegistriesEndPoint: (getRegistriesEndPoint), - getRegistryEndPoint: (getRegistryEndPoint), - deleteRegistryEndPoint: (deleteRegistryEndPoint), - updateRegistryEndPoint: (updateRegistryEndPoint) + createRegistryEndPoint, + getRegistriesEndPoint, + getRegistryEndPoint, + deleteRegistryEndPoint, + updateRegistryEndPoint } diff --git a/src/controllers/router-controller.js b/src/controllers/router-controller.js index 7c373414..8f718766 100644 --- a/src/controllers/router-controller.js +++ b/src/controllers/router-controller.js @@ -23,6 +23,6 @@ const getRouterEndPoint = async function () { } module.exports = { - upsertDefaultRouter: (upsertDefaultRouter), - getRouterEndPoint: (getRouterEndPoint) + upsertDefaultRouter, + getRouterEndPoint } diff --git a/src/controllers/secret-controller.js b/src/controllers/secret-controller.js index d4457a06..826bf34d 100644 --- a/src/controllers/secret-controller.js +++ b/src/controllers/secret-controller.js @@ -50,7 +50,7 @@ const updateSecretFromYamlEndpoint = async function (req) { const secretName = req.params.name const secretData = await YamlParserService.parseSecretFile(fileContent, { isUpdate: true, - secretName: secretName + secretName }) return SecretService.updateSecretEndpoint(secretName, secretData) } diff --git a/src/controllers/service-controller.js b/src/controllers/service-controller.js index b07df17a..e47bf68a 100644 --- a/src/controllers/service-controller.js +++ b/src/controllers/service-controller.js @@ -50,7 +50,7 @@ const updateServiceYAMLEndpoint = async function (req) { const fileContent = req.file.buffer.toString() const serviceData = await YamlParserService.parseServiceFile(fileContent, { isUpdate: true, - serviceName: serviceName + serviceName }) return ServiceService.updateServiceEndpoint(serviceName, serviceData) } diff --git a/src/controllers/tunnel-controller.js b/src/controllers/tunnel-controller.js index fb3ea34c..4cf7dad8 100644 --- a/src/controllers/tunnel-controller.js +++ b/src/controllers/tunnel-controller.js @@ -41,6 +41,6 @@ const getTunnelEndPoint = async function (req) { } module.exports = { - manageTunnelEndPoint: (manageTunnelEndPoint), - getTunnelEndPoint: (getTunnelEndPoint) + manageTunnelEndPoint, + getTunnelEndPoint } diff --git a/src/controllers/user-controller.js b/src/controllers/user-controller.js index 936964ed..d4a79482 100644 --- a/src/controllers/user-controller.js +++ b/src/controllers/user-controller.js @@ -118,21 +118,21 @@ const interactionCompleteEndPoint = async function (req, res) { } module.exports = { - userLoginEndPoint: userLoginEndPoint, - refreshTokenEndPoint: refreshTokenEndPoint, - getUserProfileEndPoint: getUserProfileEndPoint, - userLogoutEndPoint: userLogoutEndPoint, - enrollMfaEndPoint: enrollMfaEndPoint, - confirmMfaEndPoint: confirmMfaEndPoint, - disableMfaEndPoint: disableMfaEndPoint, - changePasswordEndPoint: changePasswordEndPoint, - oauthAuthorizeEndPoint: oauthAuthorizeEndPoint, - oauthCallbackEndPoint: oauthCallbackEndPoint, - interactionStatusEndPoint: interactionStatusEndPoint, - interactionLoginEndPoint: interactionLoginEndPoint, - interactionMfaEndPoint: interactionMfaEndPoint, - interactionEnrollEndPoint: interactionEnrollEndPoint, - interactionConfirmEnrollEndPoint: interactionConfirmEnrollEndPoint, - interactionChangePasswordEndPoint: interactionChangePasswordEndPoint, - interactionCompleteEndPoint: interactionCompleteEndPoint + userLoginEndPoint, + refreshTokenEndPoint, + getUserProfileEndPoint, + userLogoutEndPoint, + enrollMfaEndPoint, + confirmMfaEndPoint, + disableMfaEndPoint, + changePasswordEndPoint, + oauthAuthorizeEndPoint, + oauthCallbackEndPoint, + interactionStatusEndPoint, + interactionLoginEndPoint, + interactionMfaEndPoint, + interactionEnrollEndPoint, + interactionConfirmEnrollEndPoint, + interactionChangePasswordEndPoint, + interactionCompleteEndPoint } diff --git a/src/daemon.js b/src/daemon.js index 105ff882..5813bf08 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -19,7 +19,7 @@ const daemon = daemonize.setup({ main: 'server.js', name: 'iofog-controller', pidfile: path.join(process.env.PID_BASE || __dirname, 'iofog-controller.pid'), - argv: [ ...process.argv.slice(2), 'daemonize2' ], + argv: [...process.argv.slice(2), 'daemonize2'], silent: true }) diff --git a/src/data/managers/application-manager.js b/src/data/managers/application-manager.js index 3b546e00..f16d3d08 100644 --- a/src/data/managers/application-manager.js +++ b/src/data/managers/application-manager.js @@ -30,9 +30,9 @@ class ApplicationManager extends BaseManager { required: false } ], - where: where, + where, attributes: ['id'] - }, { transaction: transaction }) + }, { transaction }) if (!application) { return [] } @@ -41,17 +41,18 @@ class ApplicationManager extends BaseManager { async findAllWithAttributes (where, attributes, transaction) { return Application.findAll({ - where: where, - attributes: attributes }, - { transaction: transaction }) + where, + attributes + }, + { transaction }) } async findOneWithAttributes (where, attributes, transaction) { return Application.findOne({ - where: where, - attributes: attributes + where, + attributes }, - { transaction: transaction }) + { transaction }) } async findOnePopulated (where, attributes, transaction) { @@ -65,7 +66,7 @@ class ApplicationManager extends BaseManager { ], where, attributes - }, { transaction: transaction }) + }, { transaction }) if (!application) { return null } @@ -87,7 +88,7 @@ class ApplicationManager extends BaseManager { ], where, attributes - }, { transaction: transaction }) + }, { transaction }) return applications.map(application => ({ ...application.get({ plain: true }), microservices: (application.microservices || []).map(m => m.get({ plain: true })) diff --git a/src/data/managers/application-template-manager.js b/src/data/managers/application-template-manager.js index 98ef4ca0..2eb6a23c 100644 --- a/src/data/managers/application-template-manager.js +++ b/src/data/managers/application-template-manager.js @@ -31,7 +31,7 @@ class ApplicationTemplateManager extends BaseManager { ], where, attributes - }, { transaction: transaction }) + }, { transaction }) return applicationTemplate } @@ -46,7 +46,7 @@ class ApplicationTemplateManager extends BaseManager { ], where, attributes - }, { transaction: transaction }) + }, { transaction }) return applicationTemplates } } diff --git a/src/data/managers/base-manager.js b/src/data/managers/base-manager.js index 4dc4ed24..5f960ac8 100644 --- a/src/data/managers/base-manager.js +++ b/src/data/managers/base-manager.js @@ -27,21 +27,22 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? { - where: object - } + where: object + } : { - where: object, - transaction: transaction - } + where: object, + transaction + } return this.getEntity().findAll(options) } findAllWithAttributes (where, attributes, transaction) { return this.getEntity().findAll({ - where: where, - attributes: attributes }, - { transaction: transaction }) + where, + attributes + }, + { transaction }) } async findOne (object, transaction) { @@ -51,12 +52,12 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? { - where: object - } + where: object + } : { - where: object, - transaction: transaction - } + where: object, + transaction + } return this.getEntity().findOne(options) } @@ -66,7 +67,7 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? {} - : { transaction: transaction } + : { transaction } return this.getEntity().create(object, options) } @@ -76,7 +77,7 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? {} - : { transaction: transaction } + : { transaction } return this.getEntity().bulkCreate(arr, options) } @@ -88,12 +89,12 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? { - where: data - } + where: data + } : { - where: data, - transaction: transaction - } + where: data, + transaction + } return this.getEntity().destroy(options) } @@ -105,12 +106,12 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? { - where: whereData - } + where: whereData + } : { - where: whereData, - transaction: transaction - } + where: whereData, + transaction + } return this.getEntity().update(newData, options) } @@ -120,7 +121,7 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? {} - : { transaction: transaction } + : { transaction } return this.getEntity().upsert(data, options) } @@ -147,7 +148,7 @@ module.exports = class BaseManager { let hasUpdates = false for (const fldName in newData) { - if (newData.hasOwnProperty(fldName) && obj.dataValues.hasOwnProperty(fldName) && + if (Object.hasOwn(newData, fldName) && Object.hasOwn(obj.dataValues, fldName) && newData[fldName] !== obj.dataValues[fldName]) { hasUpdates = true break diff --git a/src/data/managers/catalog-item-manager.js b/src/data/managers/catalog-item-manager.js index 5f2ab3f5..90a48d36 100644 --- a/src/data/managers/catalog-item-manager.js +++ b/src/data/managers/catalog-item-manager.js @@ -44,9 +44,9 @@ class CatalogItemManager extends BaseManager { required: false, attributes: ['infoType', 'infoFormat'] }], - where: where, - attributes: attributes - }, { transaction: transaction }) + where, + attributes + }, { transaction }) } findOneWithDependencies (where, attribures, transaction) { @@ -70,9 +70,9 @@ class CatalogItemManager extends BaseManager { required: false, attributes: ['infoType', 'infoFormat'] }], - where: where, + where, attributes: attribures - }, { transaction: transaction }) + }, { transaction }) } } diff --git a/src/data/managers/certificate-manager.js b/src/data/managers/certificate-manager.js index a54c62a0..f55bf126 100644 --- a/src/data/managers/certificate-manager.js +++ b/src/data/managers/certificate-manager.js @@ -27,12 +27,14 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { signedById: caId }, - include: ['secret'] } + where: { signedById: caId }, + include: ['secret'] + } : { - where: { signedById: caId }, - include: ['secret'], - transaction: transaction } + where: { signedById: caId }, + include: ['secret'], + transaction + } return this.getEntity().findAll(options) } @@ -44,16 +46,18 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { validTo: { [Op.lt]: expirationDate + where: { + validTo: { [Op.lt]: expirationDate } + }, + include: ['signingCA'] } - }, - include: ['signingCA'] } : { - where: { validTo: { [Op.lt]: expirationDate + where: { + validTo: { [Op.lt]: expirationDate } + }, + include: ['signingCA'], + transaction } - }, - include: ['signingCA'], - transaction: transaction } return this.getEntity().findAll(options) } @@ -62,12 +66,14 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { name }, - include: ['signingCA', 'secret'] } + where: { name }, + include: ['signingCA', 'secret'] + } : { - where: { name }, - include: ['signingCA', 'secret'], - transaction: transaction } + where: { name }, + include: ['signingCA', 'secret'], + transaction + } return this.getEntity().findOne(options) } @@ -76,12 +82,14 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { isCA: true }, - include: ['secret'] } + where: { isCA: true }, + include: ['secret'] + } : { - where: { isCA: true }, - include: ['secret'], - transaction: transaction } + where: { isCA: true }, + include: ['secret'], + transaction + } return this.getEntity().findAll(options) } @@ -89,11 +97,11 @@ class CertificateManager extends BaseManager { AppHelper.checkTransaction(transaction) const options = transaction.fakeTransaction - ? { - include: ['signingCA', 'secret'] } + ? { include: ['signingCA', 'secret'] } : { - include: ['signingCA', 'secret'], - transaction: transaction } + include: ['signingCA', 'secret'], + transaction + } return this.getEntity().findAll(options) } @@ -106,11 +114,11 @@ class CertificateManager extends BaseManager { // Find existing certificate const options = transaction.fakeTransaction - ? { - where: { id } } + ? { where: { id } } : { - where: { id }, - transaction: transaction } + where: { id }, + transaction + } const cert = await this.getEntity().findOne(options) if (!cert) { @@ -128,16 +136,18 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { validTo: { [Op.lt]: currentDate + where: { + validTo: { [Op.lt]: currentDate } + }, + include: ['signingCA', 'secret'] } - }, - include: ['signingCA', 'secret'] } : { - where: { validTo: { [Op.lt]: currentDate + where: { + validTo: { [Op.lt]: currentDate } + }, + include: ['signingCA', 'secret'], + transaction } - }, - include: ['signingCA', 'secret'], - transaction: transaction } return this.getEntity().findAll(options) } @@ -147,12 +157,14 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { id: certId }, - include: ['signingCA', 'secret'] } + where: { id: certId }, + include: ['signingCA', 'secret'] + } : { - where: { id: certId }, - include: ['signingCA', 'secret'], - transaction: transaction } + where: { id: certId }, + include: ['signingCA', 'secret'], + transaction + } let currentCert = await this.getEntity().findOne(options) if (!currentCert) { @@ -164,10 +176,8 @@ class CertificateManager extends BaseManager { // Traverse up the chain of signing CAs while (currentCert.signingCA) { const parentOptions = transaction.fakeTransaction - ? { where: { id: currentCert.signedById }, include: ['signingCA', 'secret'] - } - : { where: { id: currentCert.signedById }, include: ['signingCA', 'secret'], transaction: transaction - } + ? { where: { id: currentCert.signedById }, include: ['signingCA', 'secret'] } + : { where: { id: currentCert.signedById }, include: ['signingCA', 'secret'], transaction } currentCert = await this.getEntity().findOne(parentOptions) if (currentCert) { @@ -190,22 +200,24 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { - validTo: { - [Op.gt]: now, - [Op.lt]: futureDate - } - }, - include: ['signingCA', 'secret'] } + where: { + validTo: { + [Op.gt]: now, + [Op.lt]: futureDate + } + }, + include: ['signingCA', 'secret'] + } : { - where: { - validTo: { - [Op.gt]: now, - [Op.lt]: futureDate - } - }, - include: ['signingCA', 'secret'], - transaction: transaction } + where: { + validTo: { + [Op.gt]: now, + [Op.lt]: futureDate + } + }, + include: ['signingCA', 'secret'], + transaction + } return this.getEntity().findAll(options) } @@ -214,12 +226,14 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { signedById: caId }, - include: ['secret'] } + where: { signedById: caId }, + include: ['secret'] + } : { - where: { signedById: caId }, - include: ['secret'], - transaction: transaction } + where: { signedById: caId }, + include: ['secret'], + transaction + } return this.getEntity().findAll(options) } } diff --git a/src/data/managers/config-map-manager.js b/src/data/managers/config-map-manager.js index 41d9ab43..0b61b396 100644 --- a/src/data/managers/config-map-manager.js +++ b/src/data/managers/config-map-manager.js @@ -11,9 +11,9 @@ class ConfigMapManager extends BaseManager { async createConfigMap (name, immutable, data, useVault = true, transaction) { return this.create({ name, - immutable: immutable, - useVault: useVault, - data: data + immutable, + useVault, + data }, transaction) } @@ -31,7 +31,7 @@ class ConfigMapManager extends BaseManager { existing.useVault = useVault !== null ? useVault : existing.useVault // Save the instance - this triggers beforeSave hook which handles encryption/vault - const options = transaction.fakeTransaction ? {} : { transaction: transaction } + const options = transaction.fakeTransaction ? {} : { transaction } await existing.save(options) return existing diff --git a/src/data/managers/event-manager.js b/src/data/managers/event-manager.js index 7b32bdff..f23d4f33 100644 --- a/src/data/managers/event-manager.js +++ b/src/data/managers/event-manager.js @@ -92,7 +92,7 @@ class EventManager extends BaseManager { } const options = { - where: where, + where, order: [['timestamp', 'DESC']], limit: Number(limit), // Ensure it's a number offset: Number(offset) // Ensure it's a number @@ -117,8 +117,8 @@ class EventManager extends BaseManager { return { events: limitedRows, total: count, - limit: limit, - offset: offset + limit, + offset } } diff --git a/src/data/managers/fog-used-token-manager.js b/src/data/managers/fog-used-token-manager.js index 0b9a3349..15328213 100644 --- a/src/data/managers/fog-used-token-manager.js +++ b/src/data/managers/fog-used-token-manager.js @@ -44,7 +44,7 @@ class FogUsedTokenManager { const tokenData = { jti, iofogUuid: fogUuid, - expiryTime: expiryTime + expiryTime } // Create the record with or without transaction diff --git a/src/data/managers/iofog-manager.js b/src/data/managers/iofog-manager.js index ea03b45b..a4f8c437 100644 --- a/src/data/managers/iofog-manager.js +++ b/src/data/managers/iofog-manager.js @@ -25,22 +25,24 @@ class FogManager extends BaseManager { async findAllWithTags (where, transaction) { return Fog.findAll({ - where: where, - order: [ [ 'name', 'ASC' ] ], + where, + order: [['name', 'ASC']], include: [ - { model: Tags, + { + model: Tags, as: 'tags', through: { attributes: [] } }, - { model: Architecture, + { + model: Architecture, as: 'architecture', attributes: ['id', 'name', 'image', 'description'] } ] }, { - transaction: transaction + transaction }) } @@ -48,13 +50,15 @@ class FogManager extends BaseManager { return Fog.findOne({ where, include: [ - { model: Tags, + { + model: Tags, as: 'tags', through: { attributes: [] } }, - { model: Architecture, + { + model: Architecture, as: 'architecture', attributes: ['id', 'name', 'image', 'description'] } @@ -64,10 +68,10 @@ class FogManager extends BaseManager { async findAll (where, transaction) { return Fog.findAll({ - where: where, - order: [ [ 'name', 'ASC' ] ] + where, + order: [['name', 'ASC']] }, { - transaction: transaction + transaction }) } @@ -77,7 +81,7 @@ class FogManager extends BaseManager { lastActive: timestamp }, { where: { - uuid: uuid + uuid } }) } diff --git a/src/data/managers/iofog-public-key-manager.js b/src/data/managers/iofog-public-key-manager.js index 29c8dc35..71b99983 100644 --- a/src/data/managers/iofog-public-key-manager.js +++ b/src/data/managers/iofog-public-key-manager.js @@ -24,16 +24,16 @@ class FogPublicKeyManager extends BaseManager { findByFogUuid (fogUuid, transaction) { const options = transaction.fakeTransaction ? { - where: { - iofogUuid: fogUuid + where: { + iofogUuid: fogUuid + } } - } : { - where: { - iofogUuid: fogUuid - }, - transaction: transaction - } + where: { + iofogUuid: fogUuid + }, + transaction + } return FogPublicKey.findOne(options) } @@ -42,43 +42,43 @@ class FogPublicKeyManager extends BaseManager { updateOrCreate (fogUuid, publicKey, transaction) { const options = transaction.fakeTransaction ? { - where: { - iofogUuid: fogUuid + where: { + iofogUuid: fogUuid + } } - } : { - where: { - iofogUuid: fogUuid - }, - transaction: transaction - } + where: { + iofogUuid: fogUuid + }, + transaction + } return FogPublicKey.findOne(options).then((existingKey) => { if (existingKey) { const updateOptions = transaction.fakeTransaction ? { - where: { - iofogUuid: fogUuid + where: { + iofogUuid: fogUuid + } } - } : { - where: { - iofogUuid: fogUuid - }, - transaction: transaction - } + where: { + iofogUuid: fogUuid + }, + transaction + } return FogPublicKey.update({ - publicKey: publicKey + publicKey }, updateOptions) } else { const createOptions = transaction.fakeTransaction ? {} - : { transaction: transaction } + : { transaction } return FogPublicKey.create({ iofogUuid: fogUuid, - publicKey: publicKey + publicKey }, createOptions) } }) diff --git a/src/data/managers/microservice-manager.js b/src/data/managers/microservice-manager.js index c2fd4ba6..302b6a43 100644 --- a/src/data/managers/microservice-manager.js +++ b/src/data/managers/microservice-manager.js @@ -140,9 +140,9 @@ class MicroserviceManager extends BaseManager { required: false } ], - where: where, - attributes: attributes - }, { transaction: transaction }) + where, + attributes + }, { transaction }) } findAllActiveApplicationMicroservices (iofogUuid, transaction) { @@ -246,7 +246,7 @@ class MicroserviceManager extends BaseManager { } ], where: { - iofogUuid: iofogUuid, + iofogUuid, [Op.or]: [ { @@ -262,7 +262,7 @@ class MicroserviceManager extends BaseManager { ] } - }, { transaction: transaction }) + }, { transaction }) } findOneWithDependencies (where, attributes, transaction) { @@ -356,9 +356,9 @@ class MicroserviceManager extends BaseManager { required: false } ], - where: where, - attributes: attributes - }, { transaction: transaction }) + where, + attributes + }, { transaction }) } findOneWithStatusAndCategory (where, transaction) { @@ -375,8 +375,8 @@ class MicroserviceManager extends BaseManager { attributes: ['category'] } ], - where: where - }, { transaction: transaction }) + where + }, { transaction }) } findAllWithStatuses (where, transaction) { @@ -393,8 +393,8 @@ class MicroserviceManager extends BaseManager { required: false } ], - where: where - }, { transaction: transaction }) + where + }, { transaction }) } findMicroserviceOnGet (where, transaction) { @@ -410,10 +410,11 @@ class MicroserviceManager extends BaseManager { attributes: ['id'] } ], - where: where, + where, attributes: ['uuid'] - }, { transaction: transaction }) + }, { transaction }) } + findSystemMicroserviceOnGet (where, transaction) { return Microservice.findOne({ include: [ @@ -427,10 +428,11 @@ class MicroserviceManager extends BaseManager { attributes: ['id'] } ], - where: where, + where, attributes: ['uuid'] - }, { transaction: transaction }) + }, { transaction }) } + async findOneExcludeFields (where, transaction) { return Microservice.findOne({ include: [ @@ -440,11 +442,11 @@ class MicroserviceManager extends BaseManager { required: false } ], - where: where, + where, attributes: { exclude: microserviceExcludedFields } - }, { transaction: transaction }) + }, { transaction }) } async findAllExcludeFields (where, transaction) { @@ -462,12 +464,12 @@ class MicroserviceManager extends BaseManager { required: false } ], - where: where, + where, order: [['name', 'ASC']], attributes: { exclude: microserviceExcludedFields } - }, { transaction: transaction }) + }, { transaction }) } async findAllSystemExcludeFields (where, transaction) { @@ -485,12 +487,12 @@ class MicroserviceManager extends BaseManager { required: false } ], - where: where, + where, order: [['name', 'ASC']], attributes: { exclude: microserviceExcludedFields } - }, { transaction: transaction }) + }, { transaction }) } findOneWithCategory (where, transaction) { @@ -503,8 +505,8 @@ class MicroserviceManager extends BaseManager { attributes: ['category'] } ], - where: where - }, { transaction: transaction }) + where + }, { transaction }) } } diff --git a/src/data/managers/nats-connection-manager.js b/src/data/managers/nats-connection-manager.js index 83ec7fae..94d6fa46 100644 --- a/src/data/managers/nats-connection-manager.js +++ b/src/data/managers/nats-connection-manager.js @@ -22,8 +22,8 @@ class NatsConnectionManager extends BaseManager { required: true } ], - where: where - }, { transaction: transaction }) + where + }, { transaction }) } } diff --git a/src/data/managers/rbac-cache-version-manager.js b/src/data/managers/rbac-cache-version-manager.js index 2b69a17a..dca1b5a8 100644 --- a/src/data/managers/rbac-cache-version-manager.js +++ b/src/data/managers/rbac-cache-version-manager.js @@ -37,7 +37,7 @@ class RbacCacheVersionManager extends BaseManager { _getModelOptions (transaction) { return transaction && transaction.fakeTransaction ? {} - : { transaction: transaction } + : { transaction } } _extractAffectedRows (updateResult) { diff --git a/src/data/managers/rbac-role-binding-manager.js b/src/data/managers/rbac-role-binding-manager.js index a04c1c49..ce1a323a 100644 --- a/src/data/managers/rbac-role-binding-manager.js +++ b/src/data/managers/rbac-role-binding-manager.js @@ -58,7 +58,7 @@ class RbacRoleBindingManager extends BaseManager { kind: bindingData.kind || 'RoleBinding', // namespace removed (controller manages single namespace) roleRef: bindingData.roleRef, - roleId: roleId, + roleId, subjects: bindingData.subjects || [] }, transaction) } catch (error) { diff --git a/src/data/managers/rbac-role-manager.js b/src/data/managers/rbac-role-manager.js index 43ee4191..25490c8c 100644 --- a/src/data/managers/rbac-role-manager.js +++ b/src/data/managers/rbac-role-manager.js @@ -230,7 +230,7 @@ class RbacRoleManager extends BaseManager { const findAllOptions = transaction.fakeTransaction ? { where: { roleId: role.id } } - : { where: { roleId: role.id }, transaction: transaction } + : { where: { roleId: role.id }, transaction } const rules = await RbacRoleRule.findAll(findAllOptions) return { diff --git a/src/data/managers/rbac-service-account-manager.js b/src/data/managers/rbac-service-account-manager.js index c7ce0294..b7a57d1f 100644 --- a/src/data/managers/rbac-service-account-manager.js +++ b/src/data/managers/rbac-service-account-manager.js @@ -221,7 +221,7 @@ class RbacServiceAccountManager extends BaseManager { */ async listServiceAccounts (transaction, options = {}) { AppHelper.checkTransaction(transaction) - let where = {} + const where = {} if (options.applicationName) { const application = await ApplicationManager.findOne({ name: options.applicationName }, transaction) if (!application) { diff --git a/src/data/managers/router-connection-manager.js b/src/data/managers/router-connection-manager.js index 57108ff4..4acf323c 100644 --- a/src/data/managers/router-connection-manager.js +++ b/src/data/managers/router-connection-manager.js @@ -35,8 +35,8 @@ class RouterConnectionManager extends BaseManager { required: true } ], - where: where - }, { transaction: transaction }) + where + }, { transaction }) } } diff --git a/src/data/managers/secret-manager.js b/src/data/managers/secret-manager.js index 02c51205..b75ca337 100644 --- a/src/data/managers/secret-manager.js +++ b/src/data/managers/secret-manager.js @@ -13,7 +13,7 @@ class SecretManager extends BaseManager { return this.create({ name, type, - data: data + data }, transaction) } diff --git a/src/data/managers/service-manager.js b/src/data/managers/service-manager.js index 9942b36a..a5ff00a1 100644 --- a/src/data/managers/service-manager.js +++ b/src/data/managers/service-manager.js @@ -24,10 +24,11 @@ class ServiceManager extends BaseManager { async findAllWithTags (where, transaction) { return Service.findAll({ - where: where, - order: [ [ 'name', 'ASC' ] ], + where, + order: [['name', 'ASC']], include: [ - { model: Tags, + { + model: Tags, as: 'tags', through: { attributes: [] @@ -35,7 +36,7 @@ class ServiceManager extends BaseManager { } ] }, { - transaction: transaction + transaction }) } @@ -43,7 +44,8 @@ class ServiceManager extends BaseManager { return Service.findOne({ where, include: [ - { model: Tags, + { + model: Tags, as: 'tags', through: { attributes: [] diff --git a/src/data/managers/volume-mapping-manager.js b/src/data/managers/volume-mapping-manager.js index a9e77ff7..0f4df9ac 100644 --- a/src/data/managers/volume-mapping-manager.js +++ b/src/data/managers/volume-mapping-manager.js @@ -22,9 +22,9 @@ class VolumeMappingManager extends BaseManager { findAll (where, transaction) { return VolumeMapping.findAll({ - where: where, + where, attributes: ['hostDestination', 'containerDestination', 'accessMode', 'id', 'type'] - }, { transaction: transaction }) + }, { transaction }) } } diff --git a/src/data/managers/volume-mounting-manager.js b/src/data/managers/volume-mounting-manager.js index b4f44384..dfe73e81 100644 --- a/src/data/managers/volume-mounting-manager.js +++ b/src/data/managers/volume-mounting-manager.js @@ -31,30 +31,30 @@ class VolumeMountingManager extends BaseManager { getAll (where, transaction) { return VolumeMount.findAll({ - where: where, + where, attributes: ['uuid', 'name', 'configMapName', 'secretName'] - }, { transaction: transaction }) + }, { transaction }) } getOne (where, transaction) { return VolumeMount.findOne({ - where: where, + where, attributes: ['uuid', 'name', 'configMapName', 'secretName', 'version'] - }, { transaction: transaction }) + }, { transaction }) } findOne (where, transaction) { return VolumeMount.findOne({ - where: where, + where, attributes: ['uuid', 'name', 'configMapName', 'secretName', 'version'] - }, { transaction: transaction }) + }, { transaction }) } findAll (where, transaction) { return VolumeMount.findAll({ - where: where, + where, attributes: ['uuid', 'name', 'configMapName', 'secretName', 'version'] - }, { transaction: transaction }) + }, { transaction }) } } diff --git a/src/data/providers/database-provider.js b/src/data/providers/database-provider.js index 1f1cffc8..836c2bbd 100644 --- a/src/data/providers/database-provider.js +++ b/src/data/providers/database-provider.js @@ -198,6 +198,7 @@ class DatabaseProvider { try { await db.query(query) } catch (err) { + logger.error(`Failed to create SchemaVersion table (${provider}):`, err) throw err } } @@ -228,7 +229,7 @@ class DatabaseProvider { // Common method to update seeder version async updateSeederVersion (db, version, provider) { switch (provider) { - case 'sqlite': + case 'sqlite': { const sqliteQuery = 'UPDATE SchemaVersion SET seeder_version = ?, updated_at = CURRENT_TIMESTAMP WHERE id = (SELECT MAX(id) FROM SchemaVersion)' return new Promise((resolve, reject) => { db.run(sqliteQuery, [version], (err) => { @@ -236,16 +237,19 @@ class DatabaseProvider { else resolve() }) }) - case 'mysql': + } + case 'mysql': { const [result] = await db.query('SELECT MAX(id) as maxId FROM SchemaVersion') const maxId = result[0].maxId const mysqlQuery = 'UPDATE SchemaVersion SET seeder_version = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?' await db.query(mysqlQuery, { replacements: [version, maxId] }) break - case 'postgres': + } + case 'postgres': { const postgresQuery = 'UPDATE "SchemaVersion" SET seeder_version = $1, updated_at = CURRENT_TIMESTAMP WHERE id = (SELECT MAX(id) FROM "SchemaVersion")' await db.query(postgresQuery, { bind: [version] }) break + } } } @@ -262,7 +266,7 @@ class DatabaseProvider { const migrationSql = fs.readFileSync(migrationSqlPath).toString() const dataArr = migrationSql.split(';') - let db = new sqlite3.Database(dbName, (err) => { + const db = new sqlite3.Database(dbName, (err) => { if (err) { logger.error(err.message) throw err @@ -468,7 +472,7 @@ class DatabaseProvider { const seederSql = fs.readFileSync(seederSqlPath).toString() const dataArr = seederSql.split(';') - let db = new sqlite3.Database(dbName, (err) => { + const db = new sqlite3.Database(dbName, (err) => { if (err) { logger.error(err.message) throw err diff --git a/src/data/providers/mysql.js b/src/data/providers/mysql.js index a899ea96..7334a84e 100644 --- a/src/data/providers/mysql.js +++ b/src/data/providers/mysql.js @@ -27,9 +27,9 @@ class MySqlDatabaseProvider extends DatabaseProvider { const caBase64 = process.env.DB_SSL_CA_B64 const sslOptions = caBase64 ? { - ca: Buffer.from(caBase64, 'base64').toString('utf-8'), - rejectUnauthorized: true - } + ca: Buffer.from(caBase64, 'base64').toString('utf-8'), + rejectUnauthorized: true + } : { rejectUnauthorized: false } connectionOptions.ssl = sslOptions @@ -54,9 +54,9 @@ class MySqlDatabaseProvider extends DatabaseProvider { const caBase64 = process.env.DB_SSL_CA_B64 sequelizeConfig.dialectOptions.ssl = caBase64 ? { - ca: Buffer.from(caBase64, 'base64').toString('utf-8'), - rejectUnauthorized: true - } + ca: Buffer.from(caBase64, 'base64').toString('utf-8'), + rejectUnauthorized: true + } : { rejectUnauthorized: false } } diff --git a/src/data/providers/postgres.js b/src/data/providers/postgres.js index bfbe071c..b7e34369 100644 --- a/src/data/providers/postgres.js +++ b/src/data/providers/postgres.js @@ -27,9 +27,9 @@ class PostgresDatabaseProvider extends DatabaseProvider { const caBase64 = process.env.DB_SSL_CA_B64 const sslOptions = caBase64 ? { - ca: Buffer.from(caBase64, 'base64').toString('utf-8'), - rejectUnauthorized: true - } + ca: Buffer.from(caBase64, 'base64').toString('utf-8'), + rejectUnauthorized: true + } : { rejectUnauthorized: false } connectionOptions.ssl = sslOptions @@ -53,9 +53,9 @@ class PostgresDatabaseProvider extends DatabaseProvider { const caBase64 = process.env.DB_SSL_CA_B64 sequelizeConfig.dialectOptions.ssl = caBase64 ? { - ca: Buffer.from(caBase64, 'base64').toString('utf-8'), - rejectUnauthorized: true - } + ca: Buffer.from(caBase64, 'base64').toString('utf-8'), + rejectUnauthorized: true + } : { rejectUnauthorized: false } } diff --git a/src/decorators/authorization-decorator.js b/src/decorators/authorization-decorator.js index 9003b0b6..0a08b0d4 100644 --- a/src/decorators/authorization-decorator.js +++ b/src/decorators/authorization-decorator.js @@ -86,5 +86,5 @@ function checkFogToken (f) { } module.exports = { - checkFogToken: checkFogToken + checkFogToken } diff --git a/src/decorators/response-decorator.js b/src/decorators/response-decorator.js index a2c7d461..1fd9c016 100644 --- a/src/decorators/response-decorator.js +++ b/src/decorators/response-decorator.js @@ -38,20 +38,19 @@ function handleErrors (f, successCode, errorsCodes) { if (errorsCodes) { errorsCodes.some((errCodeDescr) => { const isCurrentCode = errCodeDescr.errors.some((err) => { - if (errorObj instanceof err) { - return true - } + return errorObj instanceof err }) if (isCurrentCode) { code = errCodeDescr.code return true } + return false }) } code = code || 500 responseObject = { - code: code, + code, body: { name: errorObj.name, message: errorObj.message, @@ -68,5 +67,5 @@ function handleErrors (f, successCode, errorsCodes) { } module.exports = { - handleErrors: handleErrors + handleErrors } diff --git a/src/decorators/transaction-decorator.js b/src/decorators/transaction-decorator.js index a1acea95..447bf1b5 100644 --- a/src/decorators/transaction-decorator.js +++ b/src/decorators/transaction-decorator.js @@ -95,5 +95,5 @@ function generateTransaction (f, options = {}) { } module.exports = { - generateTransaction: generateTransaction + generateTransaction } diff --git a/src/helpers/app-helper.js b/src/helpers/app-helper.js index 472b7282..82f37c2c 100644 --- a/src/helpers/app-helper.js +++ b/src/helpers/app-helper.js @@ -183,7 +183,7 @@ function isTest () { function isEmpty (obj) { for (const key in obj) { - if (obj.hasOwnProperty(key)) { + if (Object.hasOwn(obj, key)) { return false } } diff --git a/src/helpers/constants.js b/src/helpers/constants.js index 9544d78a..deab8eb6 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -11,8 +11,10 @@ * */ +const path = require('path') + module.exports = { - ROOT_DIR: `${__dirname}/../..`, + ROOT_DIR: path.join(__dirname, '../..'), CMD: 'command', CMD_LIST: 'list', diff --git a/src/helpers/errors.js b/src/helpers/errors.js index 7efe4d9e..d6be8f55 100644 --- a/src/helpers/errors.js +++ b/src/helpers/errors.js @@ -135,19 +135,19 @@ class RateLimitExceededError extends Error { } module.exports = { - AuthenticationError: AuthenticationError, - TransactionError: TransactionError, - ValidationError: ValidationError, - InvalidCredentialsError: InvalidCredentialsError, - NotFoundError: NotFoundError, - ModelNotFoundError: ModelNotFoundError, - DuplicatePropertyError: DuplicatePropertyError, - FtpError: FtpError, - InvalidArgumentError: InvalidArgumentError, - InvalidArgumentTypeError: InvalidArgumentTypeError, - CLIArgsNotProvidedError: CLIArgsNotProvidedError, - ConflictError: ConflictError, - ForbiddenError: ForbiddenError, - NotImplementedError: NotImplementedError, - RateLimitExceededError: RateLimitExceededError + AuthenticationError, + TransactionError, + ValidationError, + InvalidCredentialsError, + NotFoundError, + ModelNotFoundError, + DuplicatePropertyError, + FtpError, + InvalidArgumentError, + InvalidArgumentTypeError, + CLIArgsNotProvidedError, + ConflictError, + ForbiddenError, + NotImplementedError, + RateLimitExceededError } diff --git a/src/helpers/template-helper.js b/src/helpers/template-helper.js index 89f1e9f1..21eca1e5 100755 --- a/src/helpers/template-helper.js +++ b/src/helpers/template-helper.js @@ -128,7 +128,7 @@ const rvaluesVarSubstition = async (subjects, templateContext) => { context._agentsByName = context._agentsByName || {} context._applicationsByName = context._applicationsByName || {} - for (let key in subjects) { + for (const key in subjects) { try { if (typeof subjects[key] === 'object') { await rvaluesVarSubstition(subjects[key], context, null) @@ -148,7 +148,7 @@ const rvaluesVarSubstition = async (subjects, templateContext) => { const substitutionMiddleware = async (req, res, next) => { if (['POST', 'PUT', 'PATCH'].indexOf(req.method) > -1) { // let user - let tmplContext = { + const tmplContext = { self: req.body // Private context // _user: user // need by edge resource and every on demand request diff --git a/src/routes/agent.js b/src/routes/agent.js index 8fed2025..d7798429 100644 --- a/src/routes/agent.js +++ b/src/routes/agent.js @@ -49,7 +49,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -78,7 +78,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -102,7 +102,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -131,7 +131,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -160,7 +160,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -189,7 +189,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -218,7 +218,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -247,7 +247,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -272,7 +272,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -297,7 +297,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -326,7 +326,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -362,7 +362,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -387,7 +387,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -416,7 +416,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -445,7 +445,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -474,7 +474,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -503,7 +503,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -528,7 +528,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -557,7 +557,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -581,7 +581,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { diff --git a/src/routes/application.js b/src/routes/application.js index 3f403182..b52fb33f 100644 --- a/src/routes/application.js +++ b/src/routes/application.js @@ -42,7 +42,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -70,7 +70,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -103,7 +103,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -136,7 +136,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -168,7 +168,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) return null }) } @@ -201,7 +201,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) return null }) } @@ -239,7 +239,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -276,7 +276,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -313,7 +313,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -345,7 +345,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -377,7 +377,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/applicationTemplate.js b/src/routes/applicationTemplate.js index 1f46be64..ea892da1 100644 --- a/src/routes/applicationTemplate.js +++ b/src/routes/applicationTemplate.js @@ -42,7 +42,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -74,7 +74,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -107,7 +107,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -139,7 +139,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -175,7 +175,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -212,7 +212,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -248,7 +248,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -280,7 +280,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/catalog.js b/src/routes/catalog.js index e794da14..ed3177c4 100644 --- a/src/routes/catalog.js +++ b/src/routes/catalog.js @@ -46,7 +46,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -87,7 +87,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -123,7 +123,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -168,7 +168,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -204,7 +204,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/certificate.js b/src/routes/certificate.js index b79de0b6..10bb3192 100644 --- a/src/routes/certificate.js +++ b/src/routes/certificate.js @@ -36,7 +36,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -66,7 +66,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -92,7 +92,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -122,7 +122,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -160,7 +160,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -190,7 +190,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -220,7 +220,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -246,7 +246,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -276,7 +276,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -310,7 +310,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -349,7 +349,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/cluster.js b/src/routes/cluster.js index c49595ab..db4e6fb3 100644 --- a/src/routes/cluster.js +++ b/src/routes/cluster.js @@ -41,7 +41,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -72,7 +72,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -107,7 +107,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -138,7 +138,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/config.js b/src/routes/config.js index edae06ba..412a2b57 100644 --- a/src/routes/config.js +++ b/src/routes/config.js @@ -41,7 +41,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -72,7 +72,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -104,7 +104,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/configMap.js b/src/routes/configMap.js index 3e96e2d3..81f65907 100644 --- a/src/routes/configMap.js +++ b/src/routes/configMap.js @@ -49,7 +49,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -84,7 +84,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -118,7 +118,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -153,7 +153,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -183,7 +183,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -209,7 +209,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -239,7 +239,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/controller.js b/src/routes/controller.js index 0c6bd7fe..3da79261 100644 --- a/src/routes/controller.js +++ b/src/routes/controller.js @@ -31,7 +31,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -49,7 +49,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } } ] diff --git a/src/routes/event.js b/src/routes/event.js index e9b5e082..cee2605d 100644 --- a/src/routes/event.js +++ b/src/routes/event.js @@ -46,7 +46,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -81,7 +81,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/iofog.js b/src/routes/iofog.js index 12801059..7cc0a077 100644 --- a/src/routes/iofog.js +++ b/src/routes/iofog.js @@ -45,7 +45,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -77,7 +77,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -113,7 +113,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -144,7 +144,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -175,7 +175,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -206,7 +206,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -241,7 +241,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -276,7 +276,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -307,7 +307,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -337,7 +337,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -372,7 +372,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -407,7 +407,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -442,7 +442,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, diff --git a/src/routes/microservices.js b/src/routes/microservices.js index 27a5a8b6..bae0e97b 100644 --- a/src/routes/microservices.js +++ b/src/routes/microservices.js @@ -43,7 +43,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -70,7 +70,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -102,7 +102,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -135,7 +135,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -166,7 +166,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -197,7 +197,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -233,7 +233,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -269,7 +269,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -304,7 +304,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -339,7 +339,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -376,7 +376,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -413,7 +413,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -448,7 +448,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -483,7 +483,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -518,7 +518,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -553,7 +553,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -588,7 +588,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -623,7 +623,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -654,7 +654,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -689,7 +689,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -724,7 +724,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -755,7 +755,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -786,7 +786,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -817,7 +817,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -851,7 +851,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -889,7 +889,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -927,7 +927,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -965,7 +965,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1003,7 +1003,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1041,7 +1041,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1079,7 +1079,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1117,7 +1117,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1155,7 +1155,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1190,7 +1190,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1225,7 +1225,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, diff --git a/src/routes/nats.js b/src/routes/nats.js index 20aaa59d..7adaba00 100644 --- a/src/routes/nats.js +++ b/src/routes/nats.js @@ -38,7 +38,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -62,7 +62,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -88,7 +88,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -113,7 +113,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -139,7 +139,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -163,7 +163,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -187,7 +187,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -211,7 +211,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -236,7 +236,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -262,7 +262,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -288,7 +288,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -315,7 +315,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -340,7 +340,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -364,7 +364,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -389,7 +389,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -415,7 +415,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -441,7 +441,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -468,7 +468,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -493,7 +493,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -518,7 +518,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -544,7 +544,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -569,7 +569,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -595,7 +595,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -620,7 +620,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -646,7 +646,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -672,7 +672,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -698,7 +698,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/rbac.js b/src/routes/rbac.js index 77d97699..85966de8 100644 --- a/src/routes/rbac.js +++ b/src/routes/rbac.js @@ -42,7 +42,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -76,7 +76,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -111,7 +111,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -141,7 +141,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -175,7 +175,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -210,7 +210,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -240,7 +240,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -267,7 +267,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -301,7 +301,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -336,7 +336,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -366,7 +366,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -400,7 +400,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -435,7 +435,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -465,7 +465,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -492,7 +492,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -526,7 +526,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -561,7 +561,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -591,7 +591,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -625,7 +625,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -660,7 +660,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -694,7 +694,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/registries.js b/src/routes/registries.js index 3648a6ff..d0bf9e2b 100644 --- a/src/routes/registries.js +++ b/src/routes/registries.js @@ -45,7 +45,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -75,7 +75,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -109,7 +109,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -145,7 +145,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -180,7 +180,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/router.js b/src/routes/router.js index dbcfa0d3..2b29ef30 100644 --- a/src/routes/router.js +++ b/src/routes/router.js @@ -49,7 +49,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -85,7 +85,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/secret.js b/src/routes/secret.js index cda187aa..c4dbff8a 100644 --- a/src/routes/secret.js +++ b/src/routes/secret.js @@ -49,7 +49,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -84,7 +84,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -118,7 +118,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -153,7 +153,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -183,7 +183,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -209,7 +209,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -239,7 +239,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/service.js b/src/routes/service.js index 64163af7..7247a783 100644 --- a/src/routes/service.js +++ b/src/routes/service.js @@ -45,7 +45,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -79,7 +79,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -117,7 +117,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -155,7 +155,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -189,7 +189,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -228,7 +228,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -267,7 +267,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/tunnel.js b/src/routes/tunnel.js index c9f115b5..ce286eed 100644 --- a/src/routes/tunnel.js +++ b/src/routes/tunnel.js @@ -53,7 +53,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -88,7 +88,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/user.js b/src/routes/user.js index 5ad4cd09..4464ea69 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -230,7 +230,7 @@ module.exports = [ const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : undefined - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) } }, { diff --git a/src/routes/volumeMount.js b/src/routes/volumeMount.js index a8af91cb..0e8d1f15 100644 --- a/src/routes/volumeMount.js +++ b/src/routes/volumeMount.js @@ -41,7 +41,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -72,7 +72,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -108,7 +108,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -143,7 +143,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -175,7 +175,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -207,7 +207,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -243,7 +243,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -274,7 +274,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -305,7 +305,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -336,7 +336,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/schemas/agent.js b/src/schemas/agent.js index 2c32e24d..023f155d 100644 --- a/src/schemas/agent.js +++ b/src/schemas/agent.js @@ -12,154 +12,154 @@ */ const agentProvision = { - 'id': '/agentProvision', - 'type': 'object', - 'properties': { - 'type': { 'type': 'integer', 'minimum': 0, 'maximum': 2 }, - 'key': { 'type': 'string' } + id: '/agentProvision', + type: 'object', + properties: { + type: { type: 'integer', minimum: 0, maximum: 2 }, + key: { type: 'string' } }, - 'required': ['type', 'key'], - 'additionalProperties': true + required: ['type', 'key'], + additionalProperties: true } const agentDeprovision = { - 'id': '/agentDeprovision', - 'type': 'object', - 'properties': { - 'microserviceUuids': { - 'type': 'array', - 'items': { 'type': 'string' } + id: '/agentDeprovision', + type: 'object', + properties: { + microserviceUuids: { + type: 'array', + items: { type: 'string' } } }, - 'required': ['microserviceUuids'], - 'additionalProperties': true + required: ['microserviceUuids'], + additionalProperties: true } const updateAgentConfig = { - 'id': '/updateAgentConfig', - 'type': 'object', - 'properties': { - 'networkInterface': { 'type': 'string' }, - 'containerEngineUrl': { 'type': 'string' }, - 'diskLimit': { 'type': 'integer', 'minimum': 0 }, - 'diskDirectory': { 'type': 'string' }, - 'memoryLimit': { 'type': 'integer', 'minimum': 0 }, - 'cpuLimit': { 'type': 'integer', 'minimum': 0 }, - 'logLimit': { 'type': 'integer', 'minimum': 0 }, - 'logDirectory': { 'type': 'string' }, - 'logFileCount': { 'type': 'integer', 'minimum': 0 }, - 'statusFrequency': { 'type': 'integer', 'minimum': 0 }, - 'changeFrequency': { 'type': 'integer', 'minimum': 0 }, - 'deviceScanFrequency': { 'type': 'integer', 'minimum': 0 }, - 'watchdogEnabled': { 'type': 'boolean' }, - 'latitude': { 'type': 'number', 'minimum': -90, 'maximum': 90 }, - 'longitude': { 'type': 'number', 'minimum': -180, 'maximum': 180 }, - 'gpsMode': { 'type': 'string' }, - 'gpsDevice': { 'type': 'string' }, - 'gpsScanFrequency': { 'type': 'integer', 'minimum': 0 }, - 'edgeGuardFrequency': { 'type': 'integer', 'minimum': 0 }, - 'pruningFrequency': { 'type': 'integer', 'minimum': 0 }, - 'availableDiskThreshold': { 'type': 'integer', 'minimum': 0 }, - 'logLevel': { 'type': 'string' }, - 'timeZone': { 'type': 'string' } + id: '/updateAgentConfig', + type: 'object', + properties: { + networkInterface: { type: 'string' }, + containerEngineUrl: { type: 'string' }, + diskLimit: { type: 'integer', minimum: 0 }, + diskDirectory: { type: 'string' }, + memoryLimit: { type: 'integer', minimum: 0 }, + cpuLimit: { type: 'integer', minimum: 0 }, + logLimit: { type: 'integer', minimum: 0 }, + logDirectory: { type: 'string' }, + logFileCount: { type: 'integer', minimum: 0 }, + statusFrequency: { type: 'integer', minimum: 0 }, + changeFrequency: { type: 'integer', minimum: 0 }, + deviceScanFrequency: { type: 'integer', minimum: 0 }, + watchdogEnabled: { type: 'boolean' }, + latitude: { type: 'number', minimum: -90, maximum: 90 }, + longitude: { type: 'number', minimum: -180, maximum: 180 }, + gpsMode: { type: 'string' }, + gpsDevice: { type: 'string' }, + gpsScanFrequency: { type: 'integer', minimum: 0 }, + edgeGuardFrequency: { type: 'integer', minimum: 0 }, + pruningFrequency: { type: 'integer', minimum: 0 }, + availableDiskThreshold: { type: 'integer', minimum: 0 }, + logLevel: { type: 'string' }, + timeZone: { type: 'string' } }, - 'additionalProperties': true + additionalProperties: true } const updateAgentGps = { - 'id': '/updateAgentGps', - 'type': 'object', - 'properties': { - 'latitude': { 'type': 'number', 'minimum': -90, 'maximum': 90 }, - 'longitude': { 'type': 'number', 'minimum': -180, 'maximum': 180 } + id: '/updateAgentGps', + type: 'object', + properties: { + latitude: { type: 'number', minimum: -90, maximum: 90 }, + longitude: { type: 'number', minimum: -180, maximum: 180 } }, - 'additionalProperties': true + additionalProperties: true } const updateAgentStatus = { - 'id': '/updateAgentStatus', - 'type': 'object', - 'properties': { - 'daemonStatus': { 'type': 'string' }, - 'warningMessage': { 'type': 'string' }, - 'daemonOperatingDuration': { 'type': 'integer', 'minimum': 0 }, - 'daemonLastStart': { 'type': 'integer', 'minimum': 0 }, - 'memoryUsage': { 'type': 'number', 'minimum': 0 }, - 'diskUsage': { 'type': 'number', 'minimum': 0 }, - 'cpuUsage': { 'type': 'number', 'minimum': 0 }, - 'memoryViolation': { 'type': 'boolean' }, - 'diskViolation': { 'type': 'boolean' }, - 'cpuViolation': { 'type': 'boolean' }, - 'systemAvailableDisk': { 'type': 'integer' }, - 'systemAvailableMemory': { 'type': 'integer' }, - 'systemTotalCpu': { 'type': 'number' }, - 'securityStatus': { 'type': 'string' }, - 'securityViolationInfo': { 'type': 'string' }, - 'microserviceStatus': { 'type': 'string' }, - 'repositoryCount': { 'type': 'integer', 'minimum': 0 }, - 'repositoryStatus': { 'type': 'string' }, - 'systemTime': { 'type': 'integer', 'minimum': 0 }, - 'lastStatusTime': { 'type': 'integer', 'minimum': 0 }, - 'ipAddress': { 'type': 'string' }, - 'ipAddressExternal': { 'type': 'string' }, - 'lastCommandTime': { 'type': 'integer', 'minimum': 0 }, - 'availableRuntimes': { - 'type': 'array', - 'items': { 'type': 'string' } + id: '/updateAgentStatus', + type: 'object', + properties: { + daemonStatus: { type: 'string' }, + warningMessage: { type: 'string' }, + daemonOperatingDuration: { type: 'integer', minimum: 0 }, + daemonLastStart: { type: 'integer', minimum: 0 }, + memoryUsage: { type: 'number', minimum: 0 }, + diskUsage: { type: 'number', minimum: 0 }, + cpuUsage: { type: 'number', minimum: 0 }, + memoryViolation: { type: 'boolean' }, + diskViolation: { type: 'boolean' }, + cpuViolation: { type: 'boolean' }, + systemAvailableDisk: { type: 'integer' }, + systemAvailableMemory: { type: 'integer' }, + systemTotalCpu: { type: 'number' }, + securityStatus: { type: 'string' }, + securityViolationInfo: { type: 'string' }, + microserviceStatus: { type: 'string' }, + repositoryCount: { type: 'integer', minimum: 0 }, + repositoryStatus: { type: 'string' }, + systemTime: { type: 'integer', minimum: 0 }, + lastStatusTime: { type: 'integer', minimum: 0 }, + ipAddress: { type: 'string' }, + ipAddressExternal: { type: 'string' }, + lastCommandTime: { type: 'integer', minimum: 0 }, + availableRuntimes: { + type: 'array', + items: { type: 'string' } }, - 'runtimeAgentPhase': { 'type': 'string' }, - 'controlPlaneQuiesced': { 'type': 'boolean' }, - 'gpsMode': { 'type': 'string' }, - 'gpsDevice': { 'type': 'string' }, - 'gpsScanFrequency': { 'type': 'integer', 'minimum': 0 }, - 'edgeGuardFrequency': { 'type': 'integer', 'minimum': 0 }, - 'tunnelStatus': { 'type': 'string' }, - 'version': { 'type': 'string' }, - 'isReadyToUpgrade': { 'type': 'boolean' }, - 'isReadyToRollback': { 'type': 'boolean' }, - 'gpsStatus': { 'type': 'string' } + runtimeAgentPhase: { type: 'string' }, + controlPlaneQuiesced: { type: 'boolean' }, + gpsMode: { type: 'string' }, + gpsDevice: { type: 'string' }, + gpsScanFrequency: { type: 'integer', minimum: 0 }, + edgeGuardFrequency: { type: 'integer', minimum: 0 }, + tunnelStatus: { type: 'string' }, + version: { type: 'string' }, + isReadyToUpgrade: { type: 'boolean' }, + isReadyToRollback: { type: 'boolean' }, + gpsStatus: { type: 'string' } }, - 'additionalProperties': true + additionalProperties: true } const microserviceStatus = { - 'id': '/microserviceStatus', - 'type': 'object', - 'properties': { - 'id': { 'type': 'string' }, - 'containerId': { 'type': 'string' }, - 'status': { 'type': 'string' }, - 'healthStatus': { 'type': 'string' }, - 'startTime': { 'type': 'integer' }, - 'operatingDuration': { 'type': 'integer' }, - 'cpuUsage': { 'type': 'number' }, - 'memoryUsage': { 'type': 'number' }, - 'ipAddress': { 'type': 'string' }, - 'ipAddressExternal': { 'type': 'string' }, - 'execSessionIds': { 'type': 'array', 'items': { 'type': 'string' } } + id: '/microserviceStatus', + type: 'object', + properties: { + id: { type: 'string' }, + containerId: { type: 'string' }, + status: { type: 'string' }, + healthStatus: { type: 'string' }, + startTime: { type: 'integer' }, + operatingDuration: { type: 'integer' }, + cpuUsage: { type: 'number' }, + memoryUsage: { type: 'number' }, + ipAddress: { type: 'string' }, + ipAddressExternal: { type: 'string' }, + execSessionIds: { type: 'array', items: { type: 'string' } } }, - 'required': ['id'], - 'additionalProperties': true + required: ['id'], + additionalProperties: true } const updateHardwareInfo = { - 'id': '/updateHardwareInfo', - 'type': 'object', - 'properties': { - 'info': { 'type': 'string' } + id: '/updateHardwareInfo', + type: 'object', + properties: { + info: { type: 'string' } }, - 'required': ['info'], - 'additionalProperties': true + required: ['info'], + additionalProperties: true } const updateUsbInfo = { - 'id': '/updateUsbInfo', - 'type': 'object', - 'properties': { - 'info': { 'type': 'string' } + id: '/updateUsbInfo', + type: 'object', + properties: { + info: { type: 'string' } }, - 'required': ['info'], - 'additionalProperties': true + required: ['info'], + additionalProperties: true } module.exports = { diff --git a/src/schemas/application-template.js b/src/schemas/application-template.js index 7b57779f..ba2e9b6d 100644 --- a/src/schemas/application-template.js +++ b/src/schemas/application-template.js @@ -1,95 +1,95 @@ const { nameRegex } = require('./utils/utils') const applicationTemplateCreate = { - 'id': '/applicationTemplateCreate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationTemplateCreate', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'description': { 'type': 'string' }, - 'variables': { - 'type': 'array', - 'items': { '$ref': '/applicationTemplateVariable' } + description: { type: 'string' }, + variables: { + type: 'array', + items: { $ref: '/applicationTemplateVariable' } } }, - 'required': ['name'], - 'additionalProperties': true // application will be validated once it's being deployed + required: ['name'], + additionalProperties: true // application will be validated once it's being deployed } const applicationTemplateVariable = { id: '/applicationTemplateVariable', type: 'object', properties: { - 'key': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + key: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'description': { 'type': 'string' } + description: { type: 'string' } }, additionalProperties: true, required: ['key'] } const applicationTemplateUpdate = { - 'id': '/applicationTemplateUpdate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationTemplateUpdate', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'description': { 'type': 'string' }, - 'applicationJSON': { '$ref': '/applicationCreate' }, - 'variables': { - 'type': 'array', - 'items': { '$ref': '/applicationTemplateVariable' } + description: { type: 'string' }, + applicationJSON: { $ref: '/applicationCreate' }, + variables: { + type: 'array', + items: { $ref: '/applicationTemplateVariable' } } }, - 'additionalProperties': true + additionalProperties: true } const applicationTemplatePatch = { - 'id': '/applicationTemplatePatch', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationTemplatePatch', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'description': { 'type': 'string' } + description: { type: 'string' } }, - 'additionalProperties': true + additionalProperties: true } const applicationTemplateDeploy = { - 'id': '/applicationTemplateDeploy', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationTemplateDeploy', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'description': { 'type': 'string' }, - 'isActivated': { 'type': 'boolean' }, - 'isSystem': { 'type': 'boolean' }, - 'variables': { - 'type': 'array', - 'items': { + description: { type: 'string' }, + isActivated: { type: 'boolean' }, + isSystem: { type: 'boolean' }, + variables: { + type: 'array', + items: { type: 'object', key: { type: 'string' }, value: { type: 'string' } } } }, - 'required': ['name'], - 'additionalProperties': true + required: ['name'], + additionalProperties: true } module.exports = { diff --git a/src/schemas/application.js b/src/schemas/application.js index f8175c1d..f4dec72f 100644 --- a/src/schemas/application.js +++ b/src/schemas/application.js @@ -1,73 +1,73 @@ const { nameRegex } = require('./utils/utils') const applicationCreate = { - 'id': '/applicationCreate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationCreate', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'microservices': { - 'type': 'array', - 'items': { '$ref': '/microserviceCreate' } + microservices: { + type: 'array', + items: { $ref: '/microserviceCreate' } }, - 'description': { 'type': 'string' }, - 'isActivated': { 'type': 'boolean' }, - 'isSystem': { 'type': 'boolean' }, - 'natsConfig': { '$ref': '/applicationNatsConfig' } + description: { type: 'string' }, + isActivated: { type: 'boolean' }, + isSystem: { type: 'boolean' }, + natsConfig: { $ref: '/applicationNatsConfig' } }, - 'required': ['name'], - 'additionalProperties': true + required: ['name'], + additionalProperties: true } const applicationUpdate = { - 'id': '/applicationUpdate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationUpdate', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'microservices': { - 'type': 'array', - 'items': { '$ref': '/microserviceCreate' } + microservices: { + type: 'array', + items: { $ref: '/microserviceCreate' } }, - 'description': { 'type': 'string' }, - 'isActivated': { 'type': 'boolean' }, - 'isSystem': { 'type': 'boolean' }, - 'natsConfig': { '$ref': '/applicationNatsConfig' } + description: { type: 'string' }, + isActivated: { type: 'boolean' }, + isSystem: { type: 'boolean' }, + natsConfig: { $ref: '/applicationNatsConfig' } }, - 'additionalProperties': true + additionalProperties: true } const applicationPatch = { - 'id': '/applicationPatch', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationPatch', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'description': { 'type': 'string' }, - 'isActivated': { 'type': 'boolean' }, - 'isSystem': { 'type': 'boolean' }, - 'natsConfig': { '$ref': '/applicationNatsConfig' } + description: { type: 'string' }, + isActivated: { type: 'boolean' }, + isSystem: { type: 'boolean' }, + natsConfig: { $ref: '/applicationNatsConfig' } }, - 'additionalProperties': true + additionalProperties: true } const applicationNatsConfig = { - 'id': '/applicationNatsConfig', - 'type': 'object', - 'properties': { - 'natsAccess': { 'type': 'boolean' }, - 'natsRule': { 'type': 'string', 'minLength': 1, 'maxLength': 255 } + id: '/applicationNatsConfig', + type: 'object', + properties: { + natsAccess: { type: 'boolean' }, + natsRule: { type: 'string', minLength: 1, maxLength: 255 } }, - 'additionalProperties': false + additionalProperties: false } module.exports = { diff --git a/src/schemas/catalog.js b/src/schemas/catalog.js index 244d8bd5..d36fee2f 100644 --- a/src/schemas/catalog.js +++ b/src/schemas/catalog.js @@ -12,80 +12,80 @@ */ const catalogItemCreate = { - 'id': '/catalogItemCreate', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string', 'minLength': 1 }, - 'description': { 'type': 'string' }, - 'category': { 'type': 'string' }, - 'publisher': { 'type': 'string' }, - 'diskRequired': { 'type': 'integer' }, - 'ramRequired': { 'type': 'integer' }, - 'picture': { 'type': 'string' }, - 'isPublic': { 'type': 'boolean' }, - 'registryId': { 'type': 'integer' }, - 'configExample': { 'type': 'string' }, - 'images': { - 'type': 'array', - 'minItems': 1, - 'maxItems': 4, - 'items': { '$ref': '/image' } + id: '/catalogItemCreate', + type: 'object', + properties: { + name: { type: 'string', minLength: 1 }, + description: { type: 'string' }, + category: { type: 'string' }, + publisher: { type: 'string' }, + diskRequired: { type: 'integer' }, + ramRequired: { type: 'integer' }, + picture: { type: 'string' }, + isPublic: { type: 'boolean' }, + registryId: { type: 'integer' }, + configExample: { type: 'string' }, + images: { + type: 'array', + minItems: 1, + maxItems: 4, + items: { $ref: '/image' } }, - 'inputType': { '$ref': '/type' }, - 'outputType': { '$ref': '/type' } + inputType: { $ref: '/type' }, + outputType: { $ref: '/type' } }, - 'required': ['name', 'registryId', 'images'], - 'additionalProperties': true + required: ['name', 'registryId', 'images'], + additionalProperties: true } const catalogItemUpdate = { - 'id': '/catalogItemUpdate', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string', 'minLength': 1 }, - 'description': { 'type': 'string' }, - 'category': { 'type': 'string' }, - 'publisher': { 'type': 'string' }, - 'diskRequired': { 'type': 'integer' }, - 'ramRequired': { 'type': 'integer' }, - 'picture': { 'type': 'string' }, - 'isPublic': { 'type': 'boolean' }, - 'registryId': { 'type': 'integer' }, - 'configExample': { 'type': 'string' }, - 'images': { - 'type': 'array', - 'maxItems': 4, - 'items': { '$ref': '/image' } + id: '/catalogItemUpdate', + type: 'object', + properties: { + name: { type: 'string', minLength: 1 }, + description: { type: 'string' }, + category: { type: 'string' }, + publisher: { type: 'string' }, + diskRequired: { type: 'integer' }, + ramRequired: { type: 'integer' }, + picture: { type: 'string' }, + isPublic: { type: 'boolean' }, + registryId: { type: 'integer' }, + configExample: { type: 'string' }, + images: { + type: 'array', + maxItems: 4, + items: { $ref: '/image' } }, - 'inputType': { '$ref': '/type' }, - 'outputType': { '$ref': '/type' } + inputType: { $ref: '/type' }, + outputType: { $ref: '/type' } }, - 'additionalProperties': true + additionalProperties: true } const image = { - 'id': '/image', - 'type': 'object', - 'properties': { - 'containerImage': { 'type': 'string' }, - 'archId': { - 'type': 'integer', - 'minimum': 1, - 'maximum': 4 + id: '/image', + type: 'object', + properties: { + containerImage: { type: 'string' }, + archId: { + type: 'integer', + minimum: 1, + maximum: 4 } }, - 'required': ['containerImage', 'archId'], - 'additionalProperties': true + required: ['containerImage', 'archId'], + additionalProperties: true } const type = { - 'id': '/type', - 'type': 'object', - 'properties': { - 'infoType': { 'type': 'string' }, - 'infoFormat': { 'type': 'string' } + id: '/type', + type: 'object', + properties: { + infoType: { type: 'string' }, + infoFormat: { type: 'string' } }, - 'additionalProperties': true + additionalProperties: true } module.exports = { diff --git a/src/schemas/cluster-controller.js b/src/schemas/cluster-controller.js index e3fb335a..18b00f30 100644 --- a/src/schemas/cluster-controller.js +++ b/src/schemas/cluster-controller.js @@ -12,14 +12,14 @@ */ const clusterControllerUpdate = { - 'id': '/clusterControllerUpdate', - 'type': 'object', - 'properties': { - 'host': { - 'type': 'string' + id: '/clusterControllerUpdate', + type: 'object', + properties: { + host: { + type: 'string' } }, - 'additionalProperties': false + additionalProperties: false } module.exports = { diff --git a/src/schemas/controller-register.js b/src/schemas/controller-register.js index ef84046e..76cf3ddf 100644 --- a/src/schemas/controller-register.js +++ b/src/schemas/controller-register.js @@ -12,36 +12,36 @@ */ const controllerRegister = { - 'id': '/controllerRegister', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' }, - 'name': { 'type': 'string', 'enum': ['controller'] }, - 'images': { - 'type': 'array', - 'minItems': 1, - 'maxItems': 4, - 'items': { '$ref': '/image' } + id: '/controllerRegister', + type: 'object', + properties: { + uuid: { type: 'string' }, + name: { type: 'string', enum: ['controller'] }, + images: { + type: 'array', + minItems: 1, + maxItems: 4, + items: { $ref: '/image' } }, - 'registryId': { 'type': 'integer' }, - 'ports': { - 'type': 'array', - 'items': { '$ref': '/ports' } + registryId: { type: 'integer' }, + ports: { + type: 'array', + items: { $ref: '/ports' } }, - 'volumeMappings': { - 'type': 'array', - 'items': { '$ref': '/volumeMappings' } + volumeMappings: { + type: 'array', + items: { $ref: '/volumeMappings' } }, - 'env': { - 'type': 'array', - 'items': { '$ref': '/env' } + env: { + type: 'array', + items: { $ref: '/env' } }, - 'config': { 'type': 'string' }, - 'hostNetworkMode': { 'type': 'boolean' }, - 'runtime': { 'type': 'string' } + config: { type: 'string' }, + hostNetworkMode: { type: 'boolean' }, + runtime: { type: 'string' } }, - 'required': ['uuid', 'images', 'registryId'], - 'additionalProperties': false + required: ['uuid', 'images', 'registryId'], + additionalProperties: false } module.exports = { diff --git a/src/schemas/iofog.js b/src/schemas/iofog.js index 9d9a7733..4683c3a7 100644 --- a/src/schemas/iofog.js +++ b/src/schemas/iofog.js @@ -12,173 +12,173 @@ */ const iofogCreate = { - 'id': '/iofogCreate', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string', 'minLength': 1 }, - 'location': { 'type': 'string' }, - 'latitude': { 'type': 'number', 'minimum': -90, 'maximum': 90 }, - 'longitude': { 'type': 'number', 'minimum': -180, 'maximum': 180 }, - 'description': { 'type': 'string' }, - 'networkInterface': { 'type': 'string' }, - 'containerEngineUrl': { 'type': 'string' }, - 'containerEngine': { 'type': 'string', 'enum': ['edgelet', 'docker', 'podman'] }, - 'deploymentType': { 'type': 'string', 'enum': ['native', 'container'] }, - 'diskLimit': { 'type': 'integer', 'minimum': 0 }, - 'diskDirectory': { 'type': 'string' }, - 'memoryLimit': { 'type': 'integer', 'minimum': 0 }, - 'cpuLimit': { 'type': 'integer', 'minimum': 0 }, - 'logLimit': { 'type': 'integer', 'minimum': 0 }, - 'logDirectory': { 'type': 'string' }, - 'logFileCount': { 'type': 'integer', 'minimum': 0 }, - 'statusFrequency': { 'type': 'integer', 'minimum': 0 }, - 'changeFrequency': { 'type': 'integer', 'minimum': 0 }, - 'deviceScanFrequency': { 'type': 'integer', 'minimum': 0 }, - 'bluetoothEnabled': { 'type': 'boolean' }, - 'watchdogEnabled': { 'type': 'boolean' }, - 'abstractedHardwareEnabled': { 'type': 'boolean' }, - 'archId': { 'type': 'integer', 'minimum': 0, 'maximum': 4 }, - 'pruningFrequency': { 'type': 'integer', 'minimum': 0 }, - 'availableDiskThreshold': { 'type': 'integer', 'minimum': 0 }, - 'logLevel': { 'type': 'string' }, - 'isSystem': { 'type': 'boolean' }, - 'routerMode': { 'enum': ['none', 'edge', 'interior'], 'default': 'edge' }, - 'messagingPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'interRouterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'edgeRouterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsMode': { 'enum': ['none', 'leaf', 'server'], 'default': 'leaf' }, - 'natsServerPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsLeafPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsClusterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsMqttPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsHttpPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'jsStorageSize': { 'type': 'string', 'pattern': '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', 'maxLength': 32 }, - 'jsMemoryStoreSize': { 'type': 'string', 'pattern': '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', 'maxLength': 32 }, - 'upstreamNatsServers': { - 'type': 'array', - 'items': { 'type': 'string', 'minLength': 1 } + id: '/iofogCreate', + type: 'object', + properties: { + name: { type: 'string', minLength: 1 }, + location: { type: 'string' }, + latitude: { type: 'number', minimum: -90, maximum: 90 }, + longitude: { type: 'number', minimum: -180, maximum: 180 }, + description: { type: 'string' }, + networkInterface: { type: 'string' }, + containerEngineUrl: { type: 'string' }, + containerEngine: { type: 'string', enum: ['edgelet', 'docker', 'podman'] }, + deploymentType: { type: 'string', enum: ['native', 'container'] }, + diskLimit: { type: 'integer', minimum: 0 }, + diskDirectory: { type: 'string' }, + memoryLimit: { type: 'integer', minimum: 0 }, + cpuLimit: { type: 'integer', minimum: 0 }, + logLimit: { type: 'integer', minimum: 0 }, + logDirectory: { type: 'string' }, + logFileCount: { type: 'integer', minimum: 0 }, + statusFrequency: { type: 'integer', minimum: 0 }, + changeFrequency: { type: 'integer', minimum: 0 }, + deviceScanFrequency: { type: 'integer', minimum: 0 }, + bluetoothEnabled: { type: 'boolean' }, + watchdogEnabled: { type: 'boolean' }, + abstractedHardwareEnabled: { type: 'boolean' }, + archId: { type: 'integer', minimum: 0, maximum: 4 }, + pruningFrequency: { type: 'integer', minimum: 0 }, + availableDiskThreshold: { type: 'integer', minimum: 0 }, + logLevel: { type: 'string' }, + isSystem: { type: 'boolean' }, + routerMode: { enum: ['none', 'edge', 'interior'], default: 'edge' }, + messagingPort: { type: 'integer', minimum: 1, maximum: 65535 }, + interRouterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + edgeRouterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsMode: { enum: ['none', 'leaf', 'server'], default: 'leaf' }, + natsServerPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsLeafPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsClusterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsMqttPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsHttpPort: { type: 'integer', minimum: 1, maximum: 65535 }, + jsStorageSize: { type: 'string', pattern: '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', maxLength: 32 }, + jsMemoryStoreSize: { type: 'string', pattern: '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', maxLength: 32 }, + upstreamNatsServers: { + type: 'array', + items: { type: 'string', minLength: 1 } }, - 'host': { 'type': 'string' }, - 'tags': { - 'type': 'array', - 'items': { '$ref': '/iofogTag' } + host: { type: 'string' }, + tags: { + type: 'array', + items: { $ref: '/iofogTag' } }, - 'upstreamRouters': { - 'type': 'array', - 'items': { 'type': 'string', 'minLength': 1 } + upstreamRouters: { + type: 'array', + items: { type: 'string', minLength: 1 } }, - 'networkRouter': { 'type': 'string' }, - 'timeZone': { 'type': 'string' } + networkRouter: { type: 'string' }, + timeZone: { type: 'string' } }, - 'anyOf': [ + anyOf: [ { - 'properties': { 'routerMode': { 'const': 'interior' } }, - 'required': ['interRouterPort', 'edgeRouterPort', 'host'] + properties: { routerMode: { const: 'interior' } }, + required: ['interRouterPort', 'edgeRouterPort', 'host'] }, { - 'properties': { 'routerMode': { 'const': 'edge' } }, - 'required': ['host'] + properties: { routerMode: { const: 'edge' } }, + required: ['host'] }, { - 'properties': { 'routerMode': { 'const': 'none' } } + properties: { routerMode: { const: 'none' } } } ], - 'additionalProperties': true, - 'required': ['name', 'archId'] + additionalProperties: true, + required: ['name', 'archId'] } const iofogUpdate = { - 'id': '/iofogUpdate', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' }, - 'name': { 'type': 'string', 'minLength': 1 }, - 'location': { 'type': 'string' }, - 'latitude': { 'type': 'number', 'minimum': -90, 'maximum': 90 }, - 'longitude': { 'type': 'number', 'minimum': -180, 'maximum': 180 }, - 'description': { 'type': 'string' }, - 'networkInterface': { 'type': 'string' }, - 'containerEngineUrl': { 'type': 'string' }, - 'containerEngine': { 'type': 'string', 'enum': ['edgelet', 'docker', 'podman'] }, - 'deploymentType': { 'type': 'string', 'enum': ['native', 'container'] }, - 'diskLimit': { 'type': 'integer', 'minimum': 0 }, - 'diskDirectory': { 'type': 'string' }, - 'memoryLimit': { 'type': 'integer', 'minimum': 0 }, - 'cpuLimit': { 'type': 'integer', 'minimum': 0 }, - 'logLimit': { 'type': 'integer', 'minimum': 0 }, - 'logDirectory': { 'type': 'string' }, - 'logFileCount': { 'type': 'integer', 'minimum': 0 }, - 'statusFrequency': { 'type': 'integer', 'minimum': 0 }, - 'changeFrequency': { 'type': 'integer', 'minimum': 0 }, - 'deviceScanFrequency': { 'type': 'integer', 'minimum': 0 }, - 'bluetoothEnabled': { 'type': 'boolean' }, - 'watchdogEnabled': { 'type': 'boolean' }, - 'abstractedHardwareEnabled': { 'type': 'boolean' }, - 'archId': { 'type': 'integer', 'minimum': 0, 'maximum': 4 }, - 'pruningFrequency': { 'type': 'integer', 'minimum': 0 }, - 'availableDiskThreshold': { 'type': 'integer', 'minimum': 0 }, - 'logLevel': { 'type': 'string' }, - 'isSystem': { 'type': 'boolean' }, - 'routerMode': { 'enum': ['none', 'edge', 'interior'] }, - 'messagingPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'interRouterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'edgeRouterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsMode': { 'enum': ['none', 'leaf', 'server'] }, - 'natsServerPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsLeafPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsClusterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsMqttPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsHttpPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'jsStorageSize': { 'type': 'string', 'pattern': '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', 'maxLength': 32 }, - 'jsMemoryStoreSize': { 'type': 'string', 'pattern': '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', 'maxLength': 32 }, - 'upstreamNatsServers': { - 'type': 'array', - 'items': { 'type': 'string', 'minLength': 1 } + id: '/iofogUpdate', + type: 'object', + properties: { + uuid: { type: 'string' }, + name: { type: 'string', minLength: 1 }, + location: { type: 'string' }, + latitude: { type: 'number', minimum: -90, maximum: 90 }, + longitude: { type: 'number', minimum: -180, maximum: 180 }, + description: { type: 'string' }, + networkInterface: { type: 'string' }, + containerEngineUrl: { type: 'string' }, + containerEngine: { type: 'string', enum: ['edgelet', 'docker', 'podman'] }, + deploymentType: { type: 'string', enum: ['native', 'container'] }, + diskLimit: { type: 'integer', minimum: 0 }, + diskDirectory: { type: 'string' }, + memoryLimit: { type: 'integer', minimum: 0 }, + cpuLimit: { type: 'integer', minimum: 0 }, + logLimit: { type: 'integer', minimum: 0 }, + logDirectory: { type: 'string' }, + logFileCount: { type: 'integer', minimum: 0 }, + statusFrequency: { type: 'integer', minimum: 0 }, + changeFrequency: { type: 'integer', minimum: 0 }, + deviceScanFrequency: { type: 'integer', minimum: 0 }, + bluetoothEnabled: { type: 'boolean' }, + watchdogEnabled: { type: 'boolean' }, + abstractedHardwareEnabled: { type: 'boolean' }, + archId: { type: 'integer', minimum: 0, maximum: 4 }, + pruningFrequency: { type: 'integer', minimum: 0 }, + availableDiskThreshold: { type: 'integer', minimum: 0 }, + logLevel: { type: 'string' }, + isSystem: { type: 'boolean' }, + routerMode: { enum: ['none', 'edge', 'interior'] }, + messagingPort: { type: 'integer', minimum: 1, maximum: 65535 }, + interRouterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + edgeRouterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsMode: { enum: ['none', 'leaf', 'server'] }, + natsServerPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsLeafPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsClusterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsMqttPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsHttpPort: { type: 'integer', minimum: 1, maximum: 65535 }, + jsStorageSize: { type: 'string', pattern: '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', maxLength: 32 }, + jsMemoryStoreSize: { type: 'string', pattern: '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', maxLength: 32 }, + upstreamNatsServers: { + type: 'array', + items: { type: 'string', minLength: 1 } }, - 'host': { 'type': 'string' }, - 'upstreamRouters': { - 'type': 'array', - 'items': { 'type': 'string', 'minLength': 1 } + host: { type: 'string' }, + upstreamRouters: { + type: 'array', + items: { type: 'string', minLength: 1 } }, - 'tags': { - 'type': 'array', - 'items': { '$ref': '/iofogTag' } + tags: { + type: 'array', + items: { $ref: '/iofogTag' } }, - 'networkRouter': { 'type': 'string', 'minLength': 1 }, - 'timeZone': { 'type': 'string' } + networkRouter: { type: 'string', minLength: 1 }, + timeZone: { type: 'string' } }, - 'anyOf': [ + anyOf: [ { - 'properties': { 'routerMode': { 'const': 'interior' } }, - 'required': ['interRouterPort', 'edgeRouterPort', 'host'] + properties: { routerMode: { const: 'interior' } }, + required: ['interRouterPort', 'edgeRouterPort', 'host'] }, { - 'properties': { 'routerMode': { 'const': 'edge' } } + properties: { routerMode: { const: 'edge' } } }, { - 'properties': { 'routerMode': { 'const': 'none' } } + properties: { routerMode: { const: 'none' } } } ], - 'additionalProperties': true, - 'required': ['uuid'] + additionalProperties: true, + required: ['uuid'] } const iofogDelete = { - 'id': '/iofogDelete', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' } + id: '/iofogDelete', + type: 'object', + properties: { + uuid: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } const iofogGet = { - 'id': '/iofogGet', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' }, - 'name': { 'type': 'string' } + id: '/iofogGet', + type: 'object', + properties: { + uuid: { type: 'string' }, + name: { type: 'string' } }, oneOf: [ { @@ -188,117 +188,117 @@ const iofogGet = { required: ['name'] } ], - 'additionalProperties': true + additionalProperties: true } const iofogGenerateProvision = { - 'id': '/iofogGenerateProvision', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' } + id: '/iofogGenerateProvision', + type: 'object', + properties: { + uuid: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } const iofogSetVersionCommand = { - 'id': '/iofogSetVersionCommand', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' }, - 'versionCommand': { 'enum': ['upgrade', 'rollback'] } + id: '/iofogSetVersionCommand', + type: 'object', + properties: { + uuid: { type: 'string' }, + versionCommand: { enum: ['upgrade', 'rollback'] } }, - 'required': ['uuid', 'versionCommand'], - 'additionalProperties': true + required: ['uuid', 'versionCommand'], + additionalProperties: true } const iofogReboot = { - 'id': '/iofogReboot', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' } + id: '/iofogReboot', + type: 'object', + properties: { + uuid: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } const iofogFilters = { - 'id': '/iofogFilters', - 'type': 'array', - 'items': { '$ref': '/filter' }, - 'required': [], - 'additionalProperties': true + id: '/iofogFilters', + type: 'array', + items: { $ref: '/filter' }, + required: [], + additionalProperties: true } const filter = { - 'id': '/filter', - 'type': 'object', - 'properties': { - 'key': { 'type': 'string' }, - 'value': { 'type': 'string' }, - 'condition': { 'enum': ['has', 'equals'] } + id: '/filter', + type: 'object', + properties: { + key: { type: 'string' }, + value: { type: 'string' }, + condition: { enum: ['has', 'equals'] } }, - 'required': ['key', 'value', 'condition'], - 'additionalProperties': true + required: ['key', 'value', 'condition'], + additionalProperties: true } const halGet = { - 'id': '/halGet', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' } + id: '/halGet', + type: 'object', + properties: { + uuid: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } const iofogPrune = { - 'id': '/iofogPrune', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' } + id: '/iofogPrune', + type: 'object', + properties: { + uuid: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } const defaultRouterCreate = { - 'id': '/defaultRouterCreate', - 'type': 'object', - 'properties': { - 'messagingPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'interRouterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'edgeRouterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'host': { 'type': 'string' } + id: '/defaultRouterCreate', + type: 'object', + properties: { + messagingPort: { type: 'integer', minimum: 1, maximum: 65535 }, + interRouterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + edgeRouterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + host: { type: 'string' } }, - 'required': ['host'], - 'additionalProperties': true + required: ['host'], + additionalProperties: true } const iofogTag = { - 'id': '/iofogTag', - 'type': 'string' + id: '/iofogTag', + type: 'string' } const enableNodeExec = { - 'id': '/enableNodeExec', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' }, - 'image': { 'type': 'string' } + id: '/enableNodeExec', + type: 'object', + properties: { + uuid: { type: 'string' }, + image: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } const disableNodeExec = { - 'id': '/disableNodeExec', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' } + id: '/disableNodeExec', + type: 'object', + properties: { + uuid: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } module.exports = { diff --git a/src/schemas/microservice.js b/src/schemas/microservice.js index 0ad29c5e..76dc34bb 100644 --- a/src/schemas/microservice.js +++ b/src/schemas/microservice.js @@ -1,282 +1,297 @@ const { nameRegex } = require('./utils/utils') const microserviceCreate = { - 'id': '/microserviceCreate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'pattern': nameRegex + id: '/microserviceCreate', + type: 'object', + properties: { + name: { + type: 'string', + pattern: nameRegex }, - 'config': { 'type': 'string' }, - 'annotations': { 'type': 'string' }, - 'catalogItemId': { - 'type': 'integer', - 'minimum': 4 + config: { type: 'string' }, + annotations: { type: 'string' }, + catalogItemId: { + type: 'integer', + minimum: 4 }, - 'images': { - 'type': 'array', - 'maxItems': 4, - 'items': { '$ref': '/image' } + images: { + type: 'array', + maxItems: 4, + items: { $ref: '/image' } }, - 'registryId': { - 'type': 'integer' + registryId: { + type: 'integer' }, - 'application': { 'type': 'string' }, - 'iofogUuid': { 'type': 'string' }, - 'agentName': { 'type': 'string' }, - 'hostNetworkMode': { 'type': 'boolean' }, - 'isPrivileged': { 'type': 'boolean' }, - 'schedule': { - 'type': 'integer', - 'minimum': 0, - 'maximum': 100 + application: { type: 'string' }, + iofogUuid: { type: 'string' }, + agentName: { type: 'string' }, + hostNetworkMode: { type: 'boolean' }, + isPrivileged: { type: 'boolean' }, + schedule: { + type: 'integer', + minimum: 0, + maximum: 100 }, - 'logSize': { 'type': 'integer' }, - 'volumeMappings': { - 'type': 'array', - 'items': { '$ref': '/volumeMappings' } }, - 'ports': { - 'type': 'array', - 'items': { '$ref': '/ports' } }, - 'extraHosts': { - 'type': 'array', - 'items': { '$ref': '/extraHosts' } }, - 'env': { - 'type': 'array', - 'items': { '$ref': '/env' } }, - 'cmd': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'cdiDevices': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'capAdd': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'capDrop': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'runAsUser': { 'type': 'string' }, - 'platform': { 'type': 'string' }, - 'runtime': { 'type': 'string' }, - 'cpuSetCpus': { 'type': 'string' }, - 'memoryLimit': { 'type': 'integer' }, - 'natsConfig': { '$ref': '/microserviceNatsConfig' }, - 'healthCheck': { - 'type': 'object', - 'properties': { '$ref': '/microserviceHealthCheck' } + logSize: { type: 'integer' }, + volumeMappings: { + type: 'array', + items: { $ref: '/volumeMappings' } }, - 'serviceAccount': { - 'type': 'object', - 'properties': { - 'roleRef': { - 'type': 'object', - 'properties': { - 'kind': { 'type': 'string' }, - 'name': { 'type': 'string' }, - 'apiGroup': { 'type': 'string' } + ports: { + type: 'array', + items: { $ref: '/ports' } + }, + extraHosts: { + type: 'array', + items: { $ref: '/extraHosts' } + }, + env: { + type: 'array', + items: { $ref: '/env' } + }, + cmd: { + type: 'array', + items: { type: 'string' } + }, + cdiDevices: { + type: 'array', + items: { type: 'string' } + }, + capAdd: { + type: 'array', + items: { type: 'string' } + }, + capDrop: { + type: 'array', + items: { type: 'string' } + }, + runAsUser: { type: 'string' }, + platform: { type: 'string' }, + runtime: { type: 'string' }, + cpuSetCpus: { type: 'string' }, + memoryLimit: { type: 'integer' }, + natsConfig: { $ref: '/microserviceNatsConfig' }, + healthCheck: { + type: 'object', + properties: { $ref: '/microserviceHealthCheck' } + }, + serviceAccount: { + type: 'object', + properties: { + roleRef: { + type: 'object', + properties: { + kind: { type: 'string' }, + name: { type: 'string' }, + apiGroup: { type: 'string' } }, - 'required': ['kind', 'name'] + required: ['kind', 'name'] } }, - 'additionalProperties': false + additionalProperties: false } }, - 'required': ['name'], - 'additionalProperties': true + required: ['name'], + additionalProperties: true } const microserviceUpdate = { - 'id': '/microserviceUpdate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'pattern': nameRegex + id: '/microserviceUpdate', + type: 'object', + properties: { + name: { + type: 'string', + pattern: nameRegex + }, + config: { type: 'string' }, + annotations: { type: 'string' }, + rebuild: { type: 'boolean' }, + iofogUuid: { type: 'string' }, + agentName: { type: 'string' }, + hostNetworkMode: { type: 'boolean' }, + isPrivileged: { type: 'boolean' }, + logSize: { type: 'integer', minimum: 0 }, + schedule: { + type: 'integer', + minimum: 0, + maximum: 100 + }, + volumeMappings: { + type: 'array', + items: { $ref: '/volumeMappings' } + }, + images: { + type: 'array', + maxItems: 4, + minItems: 1, + items: { $ref: '/image' } + }, + ports: { + type: 'array', + items: { $ref: '/ports' } + }, + extraHosts: { + type: 'array', + items: { $ref: '/extraHosts' } + }, + env: { + type: 'array', + items: { $ref: '/env' } + }, + cmd: { + type: 'array', + items: { type: 'string' } }, - 'config': { 'type': 'string' }, - 'annotations': { 'type': 'string' }, - 'rebuild': { 'type': 'boolean' }, - 'iofogUuid': { 'type': 'string' }, - 'agentName': { 'type': 'string' }, - 'hostNetworkMode': { 'type': 'boolean' }, - 'isPrivileged': { 'type': 'boolean' }, - 'logSize': { 'type': 'integer', 'minimum': 0 }, - 'schedule': { - 'type': 'integer', - 'minimum': 0, - 'maximum': 100 + cdiDevices: { + type: 'array', + items: { type: 'string' } }, - 'volumeMappings': { - 'type': 'array', - 'items': { '$ref': '/volumeMappings' } + capAdd: { + type: 'array', + items: { type: 'string' } }, - 'images': { - 'type': 'array', - 'maxItems': 4, - 'minItems': 1, - 'items': { '$ref': '/image' } + capDrop: { + type: 'array', + items: { type: 'string' } }, - 'ports': { - 'type': 'array', - 'items': { '$ref': '/ports' } }, - 'extraHosts': { - 'type': 'array', - 'items': { '$ref': '/extraHosts' } }, - 'env': { - 'type': 'array', - 'items': { '$ref': '/env' } }, - 'cmd': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'cdiDevices': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'capAdd': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'capDrop': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'runAsUser': { 'type': 'string' }, - 'platform': { 'type': 'string' }, - 'runtime': { 'type': 'string' }, - 'cpuSetCpus': { 'type': 'string' }, - 'memoryLimit': { 'type': 'integer' }, - 'natsConfig': { '$ref': '/microserviceNatsConfig' }, - 'healthCheck': { - 'type': 'object', - 'properties': { '$ref': '/microserviceHealthCheck' } + runAsUser: { type: 'string' }, + platform: { type: 'string' }, + runtime: { type: 'string' }, + cpuSetCpus: { type: 'string' }, + memoryLimit: { type: 'integer' }, + natsConfig: { $ref: '/microserviceNatsConfig' }, + healthCheck: { + type: 'object', + properties: { $ref: '/microserviceHealthCheck' } }, - 'serviceAccount': { - 'type': 'object', - 'properties': { - 'roleRef': { - 'type': 'object', - 'properties': { - 'kind': { 'type': 'string' }, - 'name': { 'type': 'string' }, - 'apiGroup': { 'type': 'string' } + serviceAccount: { + type: 'object', + properties: { + roleRef: { + type: 'object', + properties: { + kind: { type: 'string' }, + name: { type: 'string' }, + apiGroup: { type: 'string' } }, - 'required': ['kind', 'name'] + required: ['kind', 'name'] } }, - 'additionalProperties': false + additionalProperties: false } }, - 'additionalProperties': true + additionalProperties: true } const microserviceDelete = { - 'id': '/microserviceDelete', - 'type': 'object', - 'properties': { - 'withCleanup': { - 'type': 'boolean' + id: '/microserviceDelete', + type: 'object', + properties: { + withCleanup: { + type: 'boolean' }, - 'additionalProperties': true + additionalProperties: true } } const env = { - 'id': '/env', - 'type': 'object', - 'properties': { - 'key': { 'type': 'string' }, - 'value': { 'type': 'string' }, - 'valueFromSecret': { 'type': 'string' }, - 'valueFromConfigMap': { 'type': 'string' } + id: '/env', + type: 'object', + properties: { + key: { type: 'string' }, + value: { type: 'string' }, + valueFromSecret: { type: 'string' }, + valueFromConfigMap: { type: 'string' } }, - 'required': ['key'], - 'oneOf': [ + required: ['key'], + oneOf: [ { - 'required': ['value'] + required: ['value'] }, { - 'required': ['valueFromSecret'] + required: ['valueFromSecret'] }, { - 'required': ['valueFromConfigMap'] + required: ['valueFromConfigMap'] } ], - 'additionalProperties': true + additionalProperties: true } const extraHosts = { - 'id': '/extraHosts', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string' }, - 'address': { 'type': 'string' } + id: '/extraHosts', + type: 'object', + properties: { + name: { type: 'string' }, + address: { type: 'string' } }, - 'required': ['name', 'address'], - 'additionalProperties': true + required: ['name', 'address'], + additionalProperties: true } const ports = { - 'id': '/ports', - 'type': 'object', - 'properties': { - 'internal': { 'type': 'integer' }, - 'external': { 'type': 'integer' }, - 'protocol': { 'enum': ['tcp', 'udp'] } + id: '/ports', + type: 'object', + properties: { + internal: { type: 'integer' }, + external: { type: 'integer' }, + protocol: { enum: ['tcp', 'udp'] } }, - 'required': ['internal', 'external'], - 'additionalProperties': true + required: ['internal', 'external'], + additionalProperties: true } const portsCreate = { - 'id': '/portsCreate', - 'type': 'object', - 'properties': { - 'internal': { 'type': 'integer' }, - 'external': { 'type': 'integer' }, - 'protocol': { 'enum': ['tcp', 'udp'] } + id: '/portsCreate', + type: 'object', + properties: { + internal: { type: 'integer' }, + external: { type: 'integer' }, + protocol: { enum: ['tcp', 'udp'] } }, - 'required': ['internal', 'external'], - 'additionalProperties': true + required: ['internal', 'external'], + additionalProperties: true } const volumeMappings = { - 'id': '/volumeMappings', - 'type': 'object', - 'properties': { - 'hostDestination': { 'type': 'string' }, - 'containerDestination': { 'type': 'string' }, - 'accessMode': { 'type': 'string' }, - 'type': { 'enum': ['volume', 'bind', 'volumeMount', 'serviceAccount'] } + id: '/volumeMappings', + type: 'object', + properties: { + hostDestination: { type: 'string' }, + containerDestination: { type: 'string' }, + accessMode: { type: 'string' }, + type: { enum: ['volume', 'bind', 'volumeMount', 'serviceAccount'] } }, - 'required': ['hostDestination', 'containerDestination', 'accessMode'], - 'additionalProperties': true + required: ['hostDestination', 'containerDestination', 'accessMode'], + additionalProperties: true } const microserviceHealthCheck = { - 'id': '/microserviceHealthCheck', - 'type': 'object', - 'properties': { - 'test': { - 'type': 'array', - 'items': { 'type': 'string' } + id: '/microserviceHealthCheck', + type: 'object', + properties: { + test: { + type: 'array', + items: { type: 'string' } }, - 'interval': { 'type': 'integer' }, - 'timeout': { 'type': 'integer' }, - 'startPeriod': { 'type': 'integer' }, - 'startInterval': { 'type': 'integer' }, - 'retries': { 'type': 'integer' } + interval: { type: 'integer' }, + timeout: { type: 'integer' }, + startPeriod: { type: 'integer' }, + startInterval: { type: 'integer' }, + retries: { type: 'integer' } }, - 'required': ['test'] + required: ['test'] } const microserviceNatsConfig = { - 'id': '/microserviceNatsConfig', - 'type': 'object', - 'properties': { - 'natsAccess': { 'type': 'boolean' }, - 'natsRule': { 'type': 'string', 'minLength': 1, 'maxLength': 255 } + id: '/microserviceNatsConfig', + type: 'object', + properties: { + natsAccess: { type: 'boolean' }, + natsRule: { type: 'string', minLength: 1, maxLength: 255 } }, - 'additionalProperties': false + additionalProperties: false } module.exports = { diff --git a/src/schemas/nats.js b/src/schemas/nats.js index 0c90b13d..06a195e5 100644 --- a/src/schemas/nats.js +++ b/src/schemas/nats.js @@ -1,42 +1,42 @@ 'use strict' const natsHubCreate = { - 'id': '/natsHubCreate', - 'type': 'object', - 'properties': { - 'host': { 'type': 'string' }, - 'serverPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'clusterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'leafPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'mqttPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'httpPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'jsStorageSize': { 'type': 'string', 'pattern': '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', 'maxLength': 32 }, - 'jsMemoryStoreSize': { 'type': 'string', 'pattern': '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', 'maxLength': 32 } + id: '/natsHubCreate', + type: 'object', + properties: { + host: { type: 'string' }, + serverPort: { type: 'integer', minimum: 1, maximum: 65535 }, + clusterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + leafPort: { type: 'integer', minimum: 1, maximum: 65535 }, + mqttPort: { type: 'integer', minimum: 1, maximum: 65535 }, + httpPort: { type: 'integer', minimum: 1, maximum: 65535 }, + jsStorageSize: { type: 'string', pattern: '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', maxLength: 32 }, + jsMemoryStoreSize: { type: 'string', pattern: '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', maxLength: 32 } }, - 'required': ['host'], - 'additionalProperties': true + required: ['host'], + additionalProperties: true } const natsAccountEnsure = { - 'id': '/natsAccountEnsure', - 'type': 'object', - 'properties': { - 'natsRule': { 'type': 'string', 'minLength': 1, 'maxLength': 255 } + id: '/natsAccountEnsure', + type: 'object', + properties: { + natsRule: { type: 'string', minLength: 1, maxLength: 255 } }, - 'required': [], - 'additionalProperties': true + required: [], + additionalProperties: true } const natsUserCreate = { - 'id': '/natsUserCreate', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string' }, - 'expiresIn': { 'type': 'string', 'pattern': '^[0-9]+[hdm]$', 'maxLength': 8 }, - 'natsRule': { 'type': 'string', 'minLength': 1, 'maxLength': 255 } + id: '/natsUserCreate', + type: 'object', + properties: { + name: { type: 'string' }, + expiresIn: { type: 'string', pattern: '^[0-9]+[hdm]$', maxLength: 8 }, + natsRule: { type: 'string', minLength: 1, maxLength: 255 } }, - 'required': ['name'], - 'additionalProperties': false + required: ['name'], + additionalProperties: false } const natsImportSchema = { @@ -78,89 +78,89 @@ const byteSizeLimitSchema = { } const natsAccountRulePayload = { - 'id': '/natsAccountRulePayload', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string', 'minLength': 1, 'maxLength': 255 }, - 'description': { 'type': 'string' }, - 'infoUrl': { 'type': 'string' }, - 'maxConnections': { 'type': 'integer', 'minimum': -1 }, - 'maxLeafNodeConnections': { 'type': 'integer', 'minimum': -1 }, - 'maxData': byteSizeLimitSchema, - 'maxExports': { 'type': 'integer', 'minimum': -1 }, - 'maxImports': { 'type': 'integer', 'minimum': -1 }, - 'maxMsgPayload': byteSizeLimitSchema, - 'maxSubscriptions': { 'type': 'integer', 'minimum': -1 }, - 'exportsAllowWildcards': { 'type': 'boolean' }, - 'disallowBearer': { 'type': 'boolean' }, - 'responsePermissions': { - 'type': 'object', - 'properties': { - 'maxMsgs': { 'type': 'integer', 'minimum': 0 }, - 'expires': { 'type': 'integer', 'minimum': 0 } + id: '/natsAccountRulePayload', + type: 'object', + properties: { + name: { type: 'string', minLength: 1, maxLength: 255 }, + description: { type: 'string' }, + infoUrl: { type: 'string' }, + maxConnections: { type: 'integer', minimum: -1 }, + maxLeafNodeConnections: { type: 'integer', minimum: -1 }, + maxData: byteSizeLimitSchema, + maxExports: { type: 'integer', minimum: -1 }, + maxImports: { type: 'integer', minimum: -1 }, + maxMsgPayload: byteSizeLimitSchema, + maxSubscriptions: { type: 'integer', minimum: -1 }, + exportsAllowWildcards: { type: 'boolean' }, + disallowBearer: { type: 'boolean' }, + responsePermissions: { + type: 'object', + properties: { + maxMsgs: { type: 'integer', minimum: 0 }, + expires: { type: 'integer', minimum: 0 } }, - 'additionalProperties': false + additionalProperties: false }, - 'respMax': { 'type': 'integer', 'minimum': 0 }, - 'respTtl': { 'type': 'integer', 'minimum': 0 }, - 'imports': { type: 'array', items: natsImportSchema }, - 'exports': { type: 'array', items: natsExportSchema }, - 'memStorage': byteSizeLimitSchema, - 'diskStorage': byteSizeLimitSchema, - 'streams': { 'type': 'integer', 'minimum': -1 }, - 'consumer': { 'type': 'integer', 'minimum': -1 }, - 'maxAckPending': { 'type': 'integer', 'minimum': -1 }, - 'memMaxStreamBytes': byteSizeLimitSchema, - 'diskMaxStreamBytes': byteSizeLimitSchema, - 'maxBytesRequired': { 'type': 'boolean' }, - 'tieredLimits': { 'type': 'object', 'additionalProperties': true }, - 'pubAllow': { 'type': 'array', 'items': { 'type': 'string' } }, - 'pubDeny': { 'type': 'array', 'items': { 'type': 'string' } }, - 'subAllow': { 'type': 'array', 'items': { 'type': 'string' } }, - 'subDeny': { 'type': 'array', 'items': { 'type': 'string' } } + respMax: { type: 'integer', minimum: 0 }, + respTtl: { type: 'integer', minimum: 0 }, + imports: { type: 'array', items: natsImportSchema }, + exports: { type: 'array', items: natsExportSchema }, + memStorage: byteSizeLimitSchema, + diskStorage: byteSizeLimitSchema, + streams: { type: 'integer', minimum: -1 }, + consumer: { type: 'integer', minimum: -1 }, + maxAckPending: { type: 'integer', minimum: -1 }, + memMaxStreamBytes: byteSizeLimitSchema, + diskMaxStreamBytes: byteSizeLimitSchema, + maxBytesRequired: { type: 'boolean' }, + tieredLimits: { type: 'object', additionalProperties: true }, + pubAllow: { type: 'array', items: { type: 'string' } }, + pubDeny: { type: 'array', items: { type: 'string' } }, + subAllow: { type: 'array', items: { type: 'string' } }, + subDeny: { type: 'array', items: { type: 'string' } } }, - 'required': [], - 'additionalProperties': false + required: [], + additionalProperties: false } const natsUserRulePayload = { - 'id': '/natsUserRulePayload', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string', 'minLength': 1, 'maxLength': 255 }, - 'description': { 'type': 'string' }, - 'maxSubscriptions': { 'type': 'integer', 'minimum': -1 }, - 'maxPayload': byteSizeLimitSchema, - 'maxData': byteSizeLimitSchema, - 'bearerToken': { 'type': 'boolean' }, - 'proxyRequired': { 'type': 'boolean' }, - 'allowedConnectionTypes': { - 'type': 'array', - 'items': { - 'type': 'string', - 'enum': ['STANDARD', 'WEBSOCKET', 'LEAFNODE', 'LEAFNODE_WS', 'MQTT', 'MQTT_WS', 'IN_PROCESS'] + id: '/natsUserRulePayload', + type: 'object', + properties: { + name: { type: 'string', minLength: 1, maxLength: 255 }, + description: { type: 'string' }, + maxSubscriptions: { type: 'integer', minimum: -1 }, + maxPayload: byteSizeLimitSchema, + maxData: byteSizeLimitSchema, + bearerToken: { type: 'boolean' }, + proxyRequired: { type: 'boolean' }, + allowedConnectionTypes: { + type: 'array', + items: { + type: 'string', + enum: ['STANDARD', 'WEBSOCKET', 'LEAFNODE', 'LEAFNODE_WS', 'MQTT', 'MQTT_WS', 'IN_PROCESS'] } }, - 'src': { 'type': 'array', 'items': { 'type': 'string' } }, - 'times': { - 'type': 'array', - 'items': { - 'type': 'object', - 'properties': { 'start': { 'type': 'string' }, 'end': { 'type': 'string' } }, - 'additionalProperties': false + src: { type: 'array', items: { type: 'string' } }, + times: { + type: 'array', + items: { + type: 'object', + properties: { start: { type: 'string' }, end: { type: 'string' } }, + additionalProperties: false } }, - 'timesLocation': { 'type': 'string' }, - 'respMax': { 'type': 'integer', 'minimum': 0 }, - 'respTtl': { 'type': 'integer', 'minimum': 0 }, - 'pubAllow': { 'type': 'array', 'items': { 'type': 'string' } }, - 'pubDeny': { 'type': 'array', 'items': { 'type': 'string' } }, - 'subAllow': { 'type': 'array', 'items': { 'type': 'string' } }, - 'subDeny': { 'type': 'array', 'items': { 'type': 'string' } }, - 'tags': { 'type': 'array', 'items': { 'type': 'string' } } + timesLocation: { type: 'string' }, + respMax: { type: 'integer', minimum: 0 }, + respTtl: { type: 'integer', minimum: 0 }, + pubAllow: { type: 'array', items: { type: 'string' } }, + pubDeny: { type: 'array', items: { type: 'string' } }, + subAllow: { type: 'array', items: { type: 'string' } }, + subDeny: { type: 'array', items: { type: 'string' } }, + tags: { type: 'array', items: { type: 'string' } } }, - 'required': [], - 'additionalProperties': false + required: [], + additionalProperties: false } module.exports = { diff --git a/src/schemas/registry.js b/src/schemas/registry.js index 4e31e986..3bae19ad 100644 --- a/src/schemas/registry.js +++ b/src/schemas/registry.js @@ -12,48 +12,48 @@ */ const registryCreate = { - 'id': '/registryCreate', - 'type': 'object', - 'properties': { - 'url': { 'type': 'string', 'minLength': 1 }, - 'isPublic': { 'type': 'boolean' }, - 'username': { 'type': 'string', 'minLength': 1 }, - 'password': { 'type': 'string' }, - 'email': { - 'type': 'string', - 'pattern': '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + + id: '/registryCreate', + type: 'object', + properties: { + url: { type: 'string', minLength: 1 }, + isPublic: { type: 'boolean' }, + username: { type: 'string', minLength: 1 }, + password: { type: 'string' }, + email: { + type: 'string', + pattern: '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + '\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$' } }, - 'required': ['url', 'isPublic', 'username', 'password', 'email'], - 'additionalProperties': true + required: ['url', 'isPublic', 'username', 'password', 'email'], + additionalProperties: true } const registryDelete = { - 'id': '/registryDelete', - 'type': 'object', - 'properties': { - 'id': { 'type': 'integer' } + id: '/registryDelete', + type: 'object', + properties: { + id: { type: 'integer' } }, - 'required': ['id'], - 'additionalProperties': true + required: ['id'], + additionalProperties: true } const registryUpdate = { - 'id': '/registryUpdate', - 'type': 'object', - 'properties': { - 'url': { 'type': 'string', 'minLength': 1 }, - 'isPublic': { 'type': 'boolean' }, - 'username': { 'type': 'string', 'minLength': 1 }, - 'password': { 'type': 'string' }, - 'email': { - 'type': 'string', - 'pattern': '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + + id: '/registryUpdate', + type: 'object', + properties: { + url: { type: 'string', minLength: 1 }, + isPublic: { type: 'boolean' }, + username: { type: 'string', minLength: 1 }, + password: { type: 'string' }, + email: { + type: 'string', + pattern: '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + '\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$' } }, - 'additionalProperties': true + additionalProperties: true } module.exports = { diff --git a/src/schemas/service.js b/src/schemas/service.js index 77066149..19f1c486 100644 --- a/src/schemas/service.js +++ b/src/schemas/service.js @@ -32,7 +32,7 @@ const serviceCreate = { }, tags: { type: 'array', - items: { '$ref': '/serviceTag' } + items: { $ref: '/serviceTag' } } } // allOf: [ @@ -78,7 +78,7 @@ const serviceUpdate = { }, tags: { type: 'array', - items: { '$ref': '/serviceTag' } + items: { $ref: '/serviceTag' } } } // allOf: [ diff --git a/src/schemas/tunnel.js b/src/schemas/tunnel.js index 1b3b0110..e2b2d9af 100644 --- a/src/schemas/tunnel.js +++ b/src/schemas/tunnel.js @@ -12,17 +12,17 @@ */ const tunnelCreate = { - 'id': '/tunnelCreate', - 'type': 'object', - 'properties': { - 'iofogUuid': { 'type': 'string' }, - 'username': { 'type': 'string', 'minLength': 1 }, - 'password': { 'type': 'string' }, - 'rsakey': { 'type': 'string' }, - 'lport': { 'type': 'integer', 'minimum': 0, 'maximum': 65535 }, - 'rport': { 'type': 'integer', 'minimum': 0, 'maximum': 65535 } + id: '/tunnelCreate', + type: 'object', + properties: { + iofogUuid: { type: 'string' }, + username: { type: 'string', minLength: 1 }, + password: { type: 'string' }, + rsakey: { type: 'string' }, + lport: { type: 'integer', minimum: 0, maximum: 65535 }, + rport: { type: 'integer', minimum: 0, maximum: 65535 } }, - 'required': ['iofogUuid', 'username', 'password', 'lport', 'rport'] + required: ['iofogUuid', 'username', 'password', 'lport', 'rport'] } module.exports = { diff --git a/src/schemas/volume-mount.js b/src/schemas/volume-mount.js index 7aabc0ed..ef64357a 100644 --- a/src/schemas/volume-mount.js +++ b/src/schemas/volume-mount.js @@ -1,88 +1,88 @@ const { serviceNameRegex } = require('./utils/utils') const volumeMountCreate = { - 'id': '/volumeMountCreate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'pattern': serviceNameRegex + id: '/volumeMountCreate', + type: 'object', + properties: { + name: { + type: 'string', + pattern: serviceNameRegex }, - 'secretName': { - 'type': 'string' + secretName: { + type: 'string' }, - 'configMapName': { - 'type': 'string' + configMapName: { + type: 'string' } }, - 'required': ['name'], - 'oneOf': [ + required: ['name'], + oneOf: [ { - 'required': ['secretName'] + required: ['secretName'] }, { - 'required': ['configMapName'] + required: ['configMapName'] } ], - 'additionalProperties': false + additionalProperties: false } const volumeMountUpdate = { - 'id': '/volumeMountUpdate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'pattern': serviceNameRegex + id: '/volumeMountUpdate', + type: 'object', + properties: { + name: { + type: 'string', + pattern: serviceNameRegex }, - 'secretName': { - 'type': 'string' + secretName: { + type: 'string' }, - 'configMapName': { - 'type': 'string' + configMapName: { + type: 'string' } }, - 'oneOf': [ + oneOf: [ { - 'required': ['secretName'] + required: ['secretName'] }, { - 'required': ['configMapName'] + required: ['configMapName'] } ], - 'additionalProperties': false + additionalProperties: false } const volumeMountLink = { - 'id': '/volumeMountLink', - 'type': 'object', - 'properties': { - 'fogUuids': { - 'type': 'array', - 'items': { - 'type': 'string' + id: '/volumeMountLink', + type: 'object', + properties: { + fogUuids: { + type: 'array', + items: { + type: 'string' }, - 'minItems': 1 + minItems: 1 } }, - 'required': ['fogUuids'], - 'additionalProperties': false + required: ['fogUuids'], + additionalProperties: false } const volumeMountUnlink = { - 'id': '/volumeMountUnlink', - 'type': 'object', - 'properties': { - 'fogUuids': { - 'type': 'array', - 'items': { - 'type': 'string' + id: '/volumeMountUnlink', + type: 'object', + properties: { + fogUuids: { + type: 'array', + items: { + type: 'string' }, - 'minItems': 1 + minItems: 1 } }, - 'required': ['fogUuids'], - 'additionalProperties': false + required: ['fogUuids'], + additionalProperties: false } module.exports = { diff --git a/src/server.js b/src/server.js index e734a60d..1f6f6ffe 100755 --- a/src/server.js +++ b/src/server.js @@ -222,8 +222,8 @@ initialize().then(() => { const sslOptions = createSSLOptions({ key: sslKey, cert: sslCert, - intermedKey: intermedKey, - isBase64: isBase64 + intermedKey, + isBase64 }) const viewerServer = https.createServer(sslOptions, apps.viewer).listen(ports.viewer, function onStart (err) { diff --git a/src/services/agent-service.js b/src/services/agent-service.js index 0b422f76..045bb1ca 100644 --- a/src/services/agent-service.js +++ b/src/services/agent-service.js @@ -108,7 +108,7 @@ const agentProvision = async function (provisionData, transaction) { return { uuid: fog.uuid, privateKey: keyPair.privateKey, - namespace: namespace + namespace } } @@ -444,7 +444,7 @@ const getAgentMicroservices = async function (fog, transaction) { uuid: microservice.uuid, name: microservice.name, application, - imageId: imageId, + imageId, config: microservice.config, annotations: microservice.annotations, rebuild: microservice.rebuild, @@ -452,7 +452,7 @@ const getAgentMicroservices = async function (fog, transaction) { isPrivileged: microservice.isPrivileged, cpuSetCpus: microservice.cpuSetCpus, memoryLimit: microservice.memoryLimit, - healthCheck: healthCheck, + healthCheck, pidMode: microservice.pidMode, ipcMode: microservice.ipcMode, runAsUser: microservice.runAsUser, @@ -514,14 +514,14 @@ const getAgentMicroservice = async function (microserviceUuid, fog, transaction) throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) } return { - microservice: microservice + microservice } } const getAgentRegistries = async function (fog, transaction) { const registries = await RegistryManager.findAll({}, transaction) return { - registries: registries + registries } } @@ -535,7 +535,7 @@ const getAgentTunnel = async function (fog, transaction) { } return { - tunnel: tunnel + tunnel } } @@ -770,8 +770,8 @@ const getAgentLinkedVolumeMounts = async function (fog, transaction) { uuid: resourceObject.uuid, name: resourceObject.name, version: resourceObject.version, - type: type, - data: data + type, + data } volumeMounts.push(responseObject) } diff --git a/src/services/application-service.js b/src/services/application-service.js index 11b4973b..6f67bee6 100644 --- a/src/services/application-service.js +++ b/src/services/application-service.js @@ -423,8 +423,8 @@ const getApplicationEndPoint = async function (conditions, isCLI, transaction) { const _checkForDuplicateName = async function (name, applicationId, transaction) { if (name) { const where = applicationId - ? { name: name, id: { [Op.ne]: applicationId } } - : { name: name } + ? { name, id: { [Op.ne]: applicationId } } + : { name } const result = await ApplicationManager.findOne(where, transaction) if (result) { @@ -470,6 +470,6 @@ module.exports = { getAllApplicationsEndPoint: TransactionDecorator.generateTransaction(getAllApplicationsEndPoint), getApplicationEndPoint: TransactionDecorator.generateTransaction(getApplicationEndPoint), getSystemApplicationEndPoint: TransactionDecorator.generateTransaction(getSystemApplicationEndPoint), - getApplication: getApplication, - getSystemApplication: getSystemApplication + getApplication, + getSystemApplication } diff --git a/src/services/application-template-service.js b/src/services/application-template-service.js index da45c2fc..f2c755ca 100644 --- a/src/services/application-template-service.js +++ b/src/services/application-template-service.js @@ -251,8 +251,8 @@ const getApplicationDataFromTemplate = async function (deploymentData, isCLI, tr const _checkForDuplicateName = async function (name, applicationId, transaction) { if (name) { const where = applicationId - ? { name: name, id: { [Op.ne]: applicationId } } - : { name: name } + ? { name, id: { [Op.ne]: applicationId } } + : { name } const result = await ApplicationTemplateManager.findOne(where, transaction) if (result) { @@ -270,6 +270,6 @@ module.exports = { getAllApplicationTemplatesEndPoint: TransactionDecorator.generateTransaction(getAllApplicationTemplatesEndPoint), getApplicationTemplateEndPoint: TransactionDecorator.generateTransaction(getApplicationTemplateEndPoint), getApplicationTemplateByName: TransactionDecorator.generateTransaction(getApplicationTemplateEndPoint), - getApplicationTemplate: getApplicationTemplate, + getApplicationTemplate, getApplicationDataFromTemplate } diff --git a/src/services/catalog-service.js b/src/services/catalog-service.js index 5e20592c..bca66847 100644 --- a/src/services/catalog-service.js +++ b/src/services/catalog-service.js @@ -47,11 +47,11 @@ const updateCatalogItemEndPoint = async function (id, data, isCLI, transaction) const where = isCLI ? { - id: id - } + id + } : { - id: id - } + id + } data.id = id await _updateCatalogItem(data, where, transaction) @@ -73,18 +73,18 @@ const listCatalogItemsEndPoint = async function (isCLI, transaction) { const catalogItems = await CatalogItemManager.findAllWithDependencies(where, attributes, transaction) return { - catalogItems: catalogItems + catalogItems } } async function getCatalogItem (id, isCLI, transaction) { const where = isCLI - ? { id: id } + ? { id } // : { // id: id, // [Op.or]: [{ category: { [Op.ne]: 'SYSTEM' } }, { category: null }] // } - : { id: id } + : { id } const attributes = isCLI ? {} @@ -99,7 +99,7 @@ async function getCatalogItem (id, isCLI, transaction) { async function getSystemCatalogItem (id, isCLI, transaction) { const where = { - id: id, + id, category: 'SYSTEM' } @@ -119,11 +119,11 @@ const getCatalogItemEndPoint = async function (id, isCLI, transaction) { const deleteCatalogItemEndPoint = async function (id, isCLI, transaction) { const where = isCLI ? { - id: id - } + id + } : { - id: id - } + id + } const item = await _checkIfItemExists(where, transaction) @@ -191,8 +191,8 @@ async function getHalCatalogItem (transaction) { const _checkForDuplicateName = async function (name, item, transaction) { if (name) { const where = (item && item.id) - ? { name: name, id: { [Op.ne]: item.id } } - : { name: name } + ? { name, id: { [Op.ne]: item.id } } + : { name } const result = await CatalogItemManager.findOne(where, transaction) if (result) { @@ -383,11 +383,11 @@ module.exports = { getCatalogItemEndPoint: TransactionDecorator.generateTransaction(getCatalogItemEndPoint), deleteCatalogItemEndPoint: TransactionDecorator.generateTransaction(deleteCatalogItemEndPoint), updateCatalogItemEndPoint: TransactionDecorator.generateTransaction(updateCatalogItemEndPoint), - getCatalogItem: getCatalogItem, - getSystemCatalogItem: getSystemCatalogItem, - getNatsCatalogItem: getNatsCatalogItem, - getBluetoothCatalogItem: getBluetoothCatalogItem, - getHalCatalogItem: getHalCatalogItem, - getRouterCatalogItem: getRouterCatalogItem, - getDebugCatalogItem: getDebugCatalogItem + getCatalogItem, + getSystemCatalogItem, + getNatsCatalogItem, + getBluetoothCatalogItem, + getHalCatalogItem, + getRouterCatalogItem, + getDebugCatalogItem } diff --git a/src/services/certificate-service.js b/src/services/certificate-service.js index 217f2c14..8e117fb9 100644 --- a/src/services/certificate-service.js +++ b/src/services/certificate-service.js @@ -10,6 +10,7 @@ const { generateSelfSignedCA, storeCA, generateCertificate } = require('../utils const config = require('../config') const Constants = require('../helpers/constants') const forge = require('node-forge') +const logger = require('../logger') // Helper function to check Kubernetes environment function checkKubernetesEnvironment () { @@ -199,7 +200,7 @@ async function getCAEndpoint (name, transaction) { serialNumber: certRecord.serialNumber, data: { certificate, - privateKey: privateKey + privateKey } } } @@ -241,6 +242,19 @@ async function deleteCAEndpoint (name, transaction) { } async function createCertificateEndpoint (certData, transaction) { + try { + return await _createCertificateEndpointInner(certData, transaction) + } catch (error) { + if (!(error instanceof Errors.ValidationError) && + !(error instanceof Errors.NotFoundError) && + !(error instanceof Errors.ConflictError)) { + logger.error(`Create certificate failed for ${certData && certData.name}:`, error.message) + } + throw error + } +} + +async function _createCertificateEndpointInner (certData, transaction) { // Validate input data const validation = await Validator.validate(certData, Validator.schemas.certificateCreate) if (!validation.valid) { @@ -322,6 +336,7 @@ async function createCertificateEndpoint (certData, transaction) { ca_name: certData.ca.secretName } } catch (error) { + logger.error(`Failed to create k8s-secret certificate ${certData.name}:`, error.message) throw error } } @@ -335,13 +350,18 @@ async function createCertificateEndpoint (certData, transaction) { } // Generate certificate - await generateCertificate({ - name: certData.name, - subject: certData.subject, - hosts: certData.hosts, - expiration: certData.expiration, - ca: certData.ca - }) + try { + await generateCertificate({ + name: certData.name, + subject: certData.subject, + hosts: certData.hosts, + expiration: certData.expiration, + ca: certData.ca + }) + } catch (error) { + logger.error(`Failed to generate certificate ${certData.name}:`, error.message) + throw error + } // Get certificate from secret to parse details const certSecret = await SecretService.getSecretEndpoint(certData.name) @@ -408,7 +428,7 @@ async function getCertificateEndpoint (name, transaction) { isExpired: certRecord.isExpired(), data: { certificate, - privateKey: privateKey + privateKey } } } @@ -454,7 +474,7 @@ async function deleteCertificateEndpoint (name, transaction) { async function renewCertificateEndpoint (name, transaction) { try { // First check if certificate exists in database - let certRecord = await CertificateManager.findCertificateByName(name, transaction) + const certRecord = await CertificateManager.findCertificateByName(name, transaction) let isNewRecord = false // If no certificate record but secret exists, we'll create a new record @@ -487,7 +507,7 @@ async function renewCertificateEndpoint (name, transaction) { // Prepare renewal data const renewalData = { - name: name, + name, subject: certRecord ? certRecord.subject : name, hosts: certRecord ? certRecord.hosts : null, isRenewal: true @@ -535,7 +555,7 @@ async function renewCertificateEndpoint (name, transaction) { if (isNewRecord) { // Create new certificate record await CertificateManager.create({ - name: name, + name, subject: renewalData.subject, hosts: renewalData.hosts, isCA: renewalData.ca.type === 'self-signed', @@ -563,7 +583,7 @@ async function renewCertificateEndpoint (name, transaction) { if (!updatedCert) { // If certificate record still doesn't exist, try to create it again with all fields await CertificateManager.create({ - name: name, + name, subject: renewalData.subject, hosts: renewalData.hosts, isCA: renewalData.ca.type === 'self-signed', @@ -609,16 +629,18 @@ async function listExpiringCertificatesEndpoint (days = 30, transaction) { // Ensure we return an empty array, not null, if no certificates are expiring return { - certificates: expiringCerts ? expiringCerts.map(cert => ({ - name: cert.name, - subject: cert.subject, - hosts: cert.hosts, - is_ca: cert.isCA, - valid_from: cert.validFrom, - valid_to: cert.validTo, - days_remaining: cert.getDaysUntilExpiration(), - ca_name: cert.signingCA ? cert.signingCA.name : null - })) : [] + certificates: expiringCerts + ? expiringCerts.map(cert => ({ + name: cert.name, + subject: cert.subject, + hosts: cert.hosts, + is_ca: cert.isCA, + valid_from: cert.validFrom, + valid_to: cert.validTo, + days_remaining: cert.getDaysUntilExpiration(), + ca_name: cert.signingCA ? cert.signingCA.name : null + })) + : [] } } diff --git a/src/services/cluster-controller-service.js b/src/services/cluster-controller-service.js index 85039018..8725c4c5 100644 --- a/src/services/cluster-controller-service.js +++ b/src/services/cluster-controller-service.js @@ -46,7 +46,7 @@ async function initializeControllerUuid (transaction) { { lastHeartbeat: new Date(), isActive: true, - processId: processId + processId }, transaction ) @@ -58,7 +58,7 @@ async function initializeControllerUuid (transaction) { await ClusterControllerManager.create({ uuid, host, - processId: processId, + processId, lastHeartbeat: new Date(), isActive: true }, transaction) diff --git a/src/services/config-map-service.js b/src/services/config-map-service.js index 25fb1859..88420558 100644 --- a/src/services/config-map-service.js +++ b/src/services/config-map-service.js @@ -138,7 +138,7 @@ async function deleteConfigMapEndpoint (configMapName, transaction) { } async function _deleteVolumeMountsUsingConfigMap (configMapName, transaction) { - const volumeMounts = await VolumeMountingManager.findAll({ configMapName: configMapName }, transaction) + const volumeMounts = await VolumeMountingManager.findAll({ configMapName }, transaction) if (volumeMounts.length > 0) { for (const volumeMount of volumeMounts) { await VolumeMountService.deleteVolumeMountEndpoint(volumeMount.name, transaction) @@ -147,12 +147,12 @@ async function _deleteVolumeMountsUsingConfigMap (configMapName, transaction) { } async function _updateChangeTrackingForFogs (configMapName, transaction) { - const configMapVolumeMounts = await VolumeMountingManager.findAll({ configMapName: configMapName }, transaction) + const configMapVolumeMounts = await VolumeMountingManager.findAll({ configMapName }, transaction) if (configMapVolumeMounts.length > 0) { for (const configMapVolumeMount of configMapVolumeMounts) { const volumeMountObj = { name: configMapVolumeMount.name, - configMapName: configMapName + configMapName } await VolumeMountService.updateVolumeMountEndpoint(configMapVolumeMount.name, volumeMountObj, transaction) } diff --git a/src/services/controller-service.js b/src/services/controller-service.js index dfe70d97..bd2513d5 100644 --- a/src/services/controller-service.js +++ b/src/services/controller-service.js @@ -44,9 +44,9 @@ const statusController = async function (isCLI) { } return { - 'status': status, - 'timestamp': Date.now(), - 'uptimeSec': process.uptime(), + status, + timestamp: Date.now(), + uptimeSec: process.uptime(), versions: { controller: packageJson.version, ecnViewer: packageJson.dependencies['@datasance/ecn-viewer'] @@ -60,6 +60,6 @@ const getVersion = async function (isCLI) { module.exports = { getArchitectures: TransactionDecorator.generateTransaction(getArchitectures), - statusController: statusController, - getVersion: getVersion + statusController, + getVersion } diff --git a/src/services/event-service.js b/src/services/event-service.js index 087694d9..203ee46c 100644 --- a/src/services/event-service.js +++ b/src/services/event-service.js @@ -390,14 +390,14 @@ async function createHttpEvent (req, res, startTime) { const eventData = { timestamp: startTime, eventType: 'HTTP', - endpointType: endpointType, - actorId: actorId, + endpointType, + actorId, method: req.method, - resourceType: resourceType, - resourceId: resourceId, + resourceType, + resourceId, endpointPath: req.path, ipAddress: captureIp ? extractIPv4Address(req) : null, - status: status, + status, statusCode: res.statusCode, statusMessage: status === 'SUCCESS' ? 'Success' : `HTTP ${res.statusCode}`, requestId: req.id || null @@ -431,10 +431,10 @@ async function createWsConnectEvent (connectionData) { const eventData = { timestamp: connectionData.timestamp || Date.now(), eventType: 'WS_CONNECT', - endpointType: endpointType, + endpointType, actorId: connectionData.actorId || null, method: 'WS', - resourceType: resourceType, + resourceType, resourceId: connectionData.resourceId || null, endpointPath: sanitizedPath, ipAddress: captureIp ? (connectionData.ipAddress || null) : null, @@ -473,14 +473,14 @@ async function createWsDisconnectEvent (connectionData) { const eventData = { timestamp: connectionData.timestamp || Date.now(), eventType: 'WS_DISCONNECT', - endpointType: endpointType, + endpointType, actorId: connectionData.actorId || null, method: 'WS', - resourceType: resourceType, + resourceType, resourceId: connectionData.resourceId || null, endpointPath: sanitizedPath, ipAddress: captureIp ? (connectionData.ipAddress || null) : null, - status: status, + status, statusCode: connectionData.closeCode || null, statusMessage: status === 'SUCCESS' ? 'WebSocket connection closed normally' : `WebSocket closed with code ${connectionData.closeCode}`, requestId: null diff --git a/src/services/iofog-service.js b/src/services/iofog-service.js index 189383e6..64749ac9 100644 --- a/src/services/iofog-service.js +++ b/src/services/iofog-service.js @@ -163,7 +163,7 @@ async function _reconcileNatsCertificatesOnHostChange (fog, transaction) { } async function _handleRouterCertificates (fogData, uuid, shouldRecreateCerts, transaction) { - logger.debug('Starting _handleRouterCertificates for fog: ' + JSON.stringify({ uuid: uuid, host: fogData.host })) + logger.debug('Starting _handleRouterCertificates for fog: ' + JSON.stringify({ uuid, host: fogData.host })) // Helper to check CA existence async function ensureCA (name, subject) { @@ -315,6 +315,17 @@ async function createFogEndPoint (fogData, isCLI, transaction) { if (isKubernetes && fogData.isSystem) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_SYSTEM_FOG_KUBERNETES)) } + + if (!isKubernetes) { + const existingFogs = await FogManager.findAll({}, transaction) + if (existingFogs.length === 0) { + fogData.isSystem = true + fogData.routerMode = 'interior' + fogData.natsMode = 'server' + logger.info('First fog in cluster — promoting to system interior router with NATS server') + } + } + let createFogData = { uuid: AppHelper.generateUUID(), name: fogData.name, @@ -377,6 +388,11 @@ async function createFogEndPoint (fogData, isCLI, transaction) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_NATS_MODE, fogData.natsMode)) } + const natsMode = fogData.natsMode || 'leaf' + if (!isCLI && !fogData.host && (fogData.routerMode !== 'none' || natsMode !== 'none')) { + throw new Errors.ValidationError(ErrorMessages.HOST_IS_REQUIRED) + } + // // TODO: handle multiple system fogs a.k.a multi-remote-controller and multi interior routers // if (fogData.isSystem && !!(await FogManager.findOne({ isSystem: true }, transaction))) { // throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.DUPLICATE_SYSTEM_FOG)) @@ -408,7 +424,7 @@ async function createFogEndPoint (fogData, isCLI, transaction) { const res = { uuid: fog.uuid } const natsConfig = { - mode: fogData.natsMode || 'leaf', + mode: natsMode, serverPort: fogData.natsServerPort, leafPort: fogData.natsLeafPort, clusterPort: fogData.natsClusterPort, @@ -432,9 +448,6 @@ async function createFogEndPoint (fogData, isCLI, transaction) { await NatsService.ensureNatsForFog(fog, natsConfig, transaction) if (fogData.routerMode !== 'none') { - if (!fogData.host && !isCLI) { - throw new Errors.ValidationError(ErrorMessages.HOST_IS_REQUIRED) - } await RouterService.createRouterForFog(fogData, fog.uuid, upstreamRouters) // Service Distribution Logic @@ -497,7 +510,7 @@ async function createFogEndPoint (fogData, isCLI, transaction) { async function _setTags (fogModel, tagsArray, transaction) { if (tagsArray) { - let tags = [] + const tags = [] for (const tag of tagsArray) { let tagModel = await TagsManager.findOne({ value: tag }, transaction) if (!tagModel) { @@ -988,12 +1001,14 @@ async function _getFogExtraInformation (fog, transaction) { } const { fogType, fogTypeId, architecture, ...fogFields } = fog const archId = fogFields.archId - const arch = architecture ? { - id: architecture.id, - name: architecture.name, - image: architecture.image, - description: architecture.description - } : undefined + const arch = architecture + ? { + id: architecture.id, + name: architecture.name, + image: architecture.image, + description: architecture.description + } + : undefined return { ...fogFields, archId, arch, tags: _mapTags(fog), ...routerConfig, ...natsConfig, volumeMounts } } @@ -1134,7 +1149,7 @@ async function generateProvisioningKeyEndPoint (fogData, isCLI, transaction) { return { key: provisioningKeyData.provisionKey, expirationTime: provisioningKeyData.expirationTime, - caCert: caCert + caCert } } @@ -1216,7 +1231,7 @@ function _filterFogs (fogs, filters) { const filtered = [] fogs.forEach((fog) => { let isMatchFog = true - filters.some((filter) => { + filters.forEach((filter) => { const fld = filter.key const val = filter.value const condition = filter.condition @@ -1224,7 +1239,6 @@ function _filterFogs (fogs, filters) { (condition === 'has' && fog[fld] && fog[fld].includes(val)) if (!isMatchField) { isMatchFog = false - return false } }) if (isMatchFog) { @@ -1698,7 +1712,7 @@ async function _createMicroserviceImages (microservice, images, transaction) { async function _updateImages (images, microserviceUuid, transaction) { await CatalogItemImageManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) return _createMicroserviceImages({ uuid: microserviceUuid }, images, transaction) } @@ -1716,7 +1730,7 @@ module.exports = { setFogRebootCommandEndPoint: TransactionDecorator.generateTransaction(setFogRebootCommandEndPoint), getHalHardwareInfoEndPoint: TransactionDecorator.generateTransaction(getHalHardwareInfoEndPoint), getHalUsbInfoEndPoint: TransactionDecorator.generateTransaction(getHalUsbInfoEndPoint), - getFog: getFog, + getFog, setFogPruneCommandEndPoint: TransactionDecorator.generateTransaction(setFogPruneCommandEndPoint), enableNodeExecEndPoint: TransactionDecorator.generateTransaction(enableNodeExecEndPoint), disableNodeExecEndPoint: TransactionDecorator.generateTransaction(disableNodeExecEndPoint), diff --git a/src/services/microservices-service.js b/src/services/microservices-service.js index 40f7bb0c..29631e1e 100644 --- a/src/services/microservices-service.js +++ b/src/services/microservices-service.js @@ -128,7 +128,7 @@ async function _ensureNatsCredsForMicroservice (microservice, transaction) { const application = microservice.application || await ApplicationManager.findOne({ id: microservice.applicationId }, transaction) const accountName = application ? application.name : account.name const credsPath = `${slugifyName(accountName, 64)}/${slugifyName(microservice.name, 64)}.creds` - const containerDest = `/etc/nats/creds` + const containerDest = '/etc/nats/creds' const existingMapping = await VolumeMappingManager.findOne({ microserviceUuid: microservice.uuid, hostDestination: credsSecretName, @@ -740,6 +740,17 @@ function _validateKeyPath (data, keyPath, resourceName, resourceType, volumeMoun * @returns {Promise} */ async function _validateVolumeMountReference (hostDestination, type, fogUuid, transaction) { + try { + return await _validateVolumeMountReferenceInner(hostDestination, type, fogUuid, transaction) + } catch (error) { + if (!(error instanceof Errors.ValidationError)) { + logger.error(`Volume mount reference validation failed (${hostDestination}, fog ${fogUuid}):`, error.message) + } + throw error + } +} + +async function _validateVolumeMountReferenceInner (hostDestination, type, fogUuid, transaction) { if (!hostDestination || typeof hostDestination !== 'string') { return // No validation needed if hostDestination is empty or not a string } @@ -772,6 +783,7 @@ async function _validateVolumeMountReference (hostDestination, type, fogUuid, tr if (error instanceof Errors.NotFoundError) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.VOLUME_MOUNT_NOT_FOUND, volumeMountName)) } + logger.error(`Failed to load volume mount ${volumeMountName}:`, error.message) throw error } @@ -799,7 +811,7 @@ async function _validateVolumeMountReference (hostDestination, type, fogUuid, tr try { linkedFogUuids = await VolumeMountService.findVolumeMountedFogNodes(volumeMountName, transaction) } catch (error) { - // If volume mount doesn't exist (shouldn't happen at this point), rethrow + logger.error(`Failed to find fog nodes linked to volume mount ${volumeMountName}:`, error.message) throw error } @@ -848,11 +860,11 @@ async function updateSystemMicroserviceEndPoint (microserviceUuid, microserviceD let needStatusReset = false const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const newFog = await _findFog(microserviceData, isCLI, transaction) || {} // validate extraHosts @@ -865,8 +877,8 @@ async function updateSystemMicroserviceEndPoint (microserviceUuid, microserviceD // const newFog = await _findFog(microserviceData, isCLI, transaction) || {} const microserviceToUpdate = { name: microserviceData.name, - config: config, - annotations: annotations, + config, + annotations, images: microserviceData.images, catalogItemId: microserviceData.catalogItemId, rebuild: microserviceData.rebuild, @@ -1137,11 +1149,11 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i let needStatusReset = false const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const newFog = await _findFog(microserviceData, isCLI, transaction) || {} // validate extraHosts @@ -1154,8 +1166,8 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i // const newFog = await _findFog(microserviceData, isCLI, transaction) || {} const microserviceToUpdate = { name: microserviceData.name, - config: config, - annotations: annotations, + config, + annotations, images: microserviceData.images, catalogItemId: microserviceData.catalogItemId, rebuild: microserviceData.rebuild, @@ -1384,7 +1396,7 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i await _updateCapDrop(microserviceDataUpdate.capDrop, microserviceUuid, transaction) } - const existingService = await ServiceManager.findOne({ type: `microservice`, resource: microservice.uuid }, transaction) + const existingService = await ServiceManager.findOne({ type: 'microservice', resource: microservice.uuid }, transaction) if (microserviceDataUpdate.iofogUuid && microserviceDataUpdate.iofogUuid !== microservice.iofogUuid && existingService) { await ServiceServices.moveMicroserviceTcpBridgeToNewFog(existingService, microserviceDataUpdate.iofogUuid, microservice.iofogUuid, transaction) } @@ -1458,11 +1470,11 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i async function updateMicroserviceConfigEndPoint (microserviceUuid, config, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithCategory(query, transaction) if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) @@ -1482,11 +1494,11 @@ async function updateMicroserviceConfigEndPoint (microserviceUuid, config, isCLI async function getMicroserviceConfigEndPoint (microserviceUuid, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithCategory(query, transaction) if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) @@ -1504,11 +1516,11 @@ async function getMicroserviceConfigEndPoint (microserviceUuid, isCLI, transacti async function deleteMicroserviceConfigEndPoint (microserviceUuid, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithCategory(query, transaction) if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) @@ -1528,11 +1540,11 @@ async function deleteMicroserviceConfigEndPoint (microserviceUuid, isCLI, transa async function getSystemMicroserviceConfigEndPoint (microserviceUuid, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithCategory(query, transaction) if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) @@ -1549,11 +1561,11 @@ async function getSystemMicroserviceConfigEndPoint (microserviceUuid, isCLI, tra async function updateSystemMicroserviceConfigEndPoint (microserviceUuid, config, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithCategory(query, transaction) if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) @@ -1573,11 +1585,11 @@ async function updateSystemMicroserviceConfigEndPoint (microserviceUuid, config, async function deleteSystemMicroserviceConfigEndPoint (microserviceUuid, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithCategory(query, transaction) if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) @@ -1597,11 +1609,11 @@ async function deleteSystemMicroserviceConfigEndPoint (microserviceUuid, isCLI, async function rebuildMicroserviceEndPoint (microserviceUuid, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const check = await MicroserviceManager.findOneWithCategory(query, transaction) if (check.catalogItem && check.catalogItem.category === 'SYSTEM') { @@ -1624,11 +1636,11 @@ async function rebuildMicroserviceEndPoint (microserviceUuid, isCLI, transaction async function rebuildSystemMicroserviceEndPoint (microserviceUuid, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.updateAndFind(query, { rebuild: true }, transaction) @@ -1655,11 +1667,11 @@ const _checkIfMicroserviceImagesAreEqual = (microserviceDataUpdateImages, catalo async function deleteMicroserviceEndPoint (microserviceUuid, microserviceData, isCLI, transaction) { const where = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithStatusAndCategory(where, transaction) if (!microservice) { @@ -1672,7 +1684,7 @@ async function deleteMicroserviceEndPoint (microserviceUuid, microserviceData, i throw new Errors.ForbiddenError(AppHelper.formatMessage(ErrorMessages.CONTROLLER_MICROSERVICE_DELETE, microserviceUuid)) } - const existingService = await ServiceManager.findOne({ type: `microservice`, resource: microservice.uuid }, transaction) + const existingService = await ServiceManager.findOne({ type: 'microservice', resource: microservice.uuid }, transaction) if (existingService) { logger.info(`Deleting service ${existingService.name}`) await ServiceServices.deleteServiceEndpoint(existingService.name, transaction) @@ -1825,7 +1837,7 @@ async function _createCdiDevices (microservice, cdiDevices, transaction) { } const msCdiDevicesData = { - cdiDevices: cdiDevices, + cdiDevices, microserviceUuid: microservice.uuid } @@ -1839,7 +1851,7 @@ async function _createCapAdd (microservice, capAdd, transaction) { } const msCapAddData = { - capAdd: capAdd, + capAdd, microserviceUuid: microservice.uuid } @@ -1853,7 +1865,7 @@ async function _createCapDrop (microservice, capDrop, transaction) { } const msCapDropData = { - capDrop: capDrop, + capDrop, microserviceUuid: microservice.uuid } @@ -1910,7 +1922,7 @@ async function createVolumeMappingEndPoint (microserviceUuid, volumeMappingData, } const volumeMapping = await VolumeMappingManager.findOne({ - microserviceUuid: microserviceUuid, + microserviceUuid, hostDestination: volumeMappingData.hostDestination, containerDestination: volumeMappingData.containerDestination, type @@ -1928,7 +1940,7 @@ async function createVolumeMappingEndPoint (microserviceUuid, volumeMappingData, } const volumeMappingObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, hostDestination: volumeMappingData.hostDestination, containerDestination: volumeMappingData.containerDestination, accessMode: volumeMappingData.accessMode, @@ -1959,7 +1971,7 @@ async function createSystemVolumeMappingEndPoint (microserviceUuid, volumeMappin } const volumeMapping = await VolumeMappingManager.findOne({ - microserviceUuid: microserviceUuid, + microserviceUuid, hostDestination: volumeMappingData.hostDestination, containerDestination: volumeMappingData.containerDestination, type @@ -1977,7 +1989,7 @@ async function createSystemVolumeMappingEndPoint (microserviceUuid, volumeMappin } const volumeMappingObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, hostDestination: volumeMappingData.hostDestination, containerDestination: volumeMappingData.containerDestination, accessMode: volumeMappingData.accessMode, @@ -1999,7 +2011,7 @@ async function deleteVolumeMappingEndPoint (microserviceUuid, volumeMappingUuid, const volumeMappingWhere = { uuid: volumeMappingUuid, - microserviceUuid: microserviceUuid + microserviceUuid } const volumeMapping = await VolumeMappingManager.findOne(volumeMappingWhere, transaction) @@ -2031,7 +2043,7 @@ async function deleteSystemVolumeMappingEndPoint (microserviceUuid, volumeMappin const volumeMappingWhere = { uuid: volumeMappingUuid, - microserviceUuid: microserviceUuid + microserviceUuid } const volumeMapping = await VolumeMappingManager.findOne(volumeMappingWhere, transaction) @@ -2061,7 +2073,7 @@ async function listVolumeMappingsEndPoint (microserviceUuid, isCLI, transaction) } const volumeMappingWhere = { - microserviceUuid: microserviceUuid + microserviceUuid } return VolumeMappingManager.findAll(volumeMappingWhere, transaction) } @@ -2131,8 +2143,8 @@ async function _createMicroservice (microserviceData, isCLI, transaction) { let newMicroservice = { uuid: AppHelper.generateUUID(), name: microserviceData.name, - config: config, - annotations: annotations, + config, + annotations, catalogItemId: microserviceData.catalogItemId, iofogUuid: microserviceData.iofogUuid, hostNetworkMode: microserviceData.hostNetworkMode, @@ -2310,13 +2322,13 @@ async function _updateVolumeMappings (volumeMappings, microserviceUuid, transact } await VolumeMappingManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const volumeMapping of volumeMappings) { const type = volumeMapping.type || VOLUME_MAPPING_DEFAULT const volumeMappingObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, hostDestination: volumeMapping.hostDestination, containerDestination: volumeMapping.containerDestination, accessMode: volumeMapping.accessMode, @@ -2331,20 +2343,20 @@ async function _updateVolumeMappings (volumeMappings, microserviceUuid, transact async function _updateImages (images, microserviceUuid, transaction) { await CatalogItemImageManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) return _createMicroserviceImages({ uuid: microserviceUuid }, images, transaction) } async function _deleteImages (microserviceUuid, transaction) { await CatalogItemImageManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) } async function _updateExtraHosts (extraHosts, microserviceUuid, transaction) { await MicroserviceExtraHostManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const extraHost of extraHosts) { await _createExtraHost({ uuid: microserviceUuid }, extraHost, transaction) @@ -2353,11 +2365,11 @@ async function _updateExtraHosts (extraHosts, microserviceUuid, transaction) { async function _updateEnv (env, microserviceUuid, transaction) { await MicroserviceEnvManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const envData of env) { const envObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, key: envData.key, value: envData.value } @@ -2411,11 +2423,11 @@ async function _updateEnv (env, microserviceUuid, transaction) { async function _updateArg (arg, microserviceUuid, transaction) { await MicroserviceArgManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const argData of arg) { const envObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, cmd: argData } @@ -2425,11 +2437,11 @@ async function _updateArg (arg, microserviceUuid, transaction) { async function _updateCdiDevices (cdiDevices, microserviceUuid, transaction) { await MicroserviceCdiDevManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const cdiDevicesData of cdiDevices) { const envObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, cdiDevices: cdiDevicesData } @@ -2439,11 +2451,11 @@ async function _updateCdiDevices (cdiDevices, microserviceUuid, transaction) { async function _updateCapAdd (capAdd, microserviceUuid, transaction) { await MicroserviceCapAddManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const capAddData of capAdd) { const envObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, capAdd: capAddData } @@ -2453,11 +2465,11 @@ async function _updateCapAdd (capAdd, microserviceUuid, transaction) { async function _updateCapDrop (capDrop, microserviceUuid, transaction) { await MicroserviceCapDropManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const capDropData of capDrop) { const envObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, capDrop: capDropData } @@ -2491,16 +2503,16 @@ async function _checkForDuplicateName (name, item, applicationId, transaction) { if (name) { const where = item.id ? { - name: name, - uuid: { [Op.ne]: item.id }, - delete: false, - applicationId - } + name, + uuid: { [Op.ne]: item.id }, + delete: false, + applicationId + } : { - name: name, - applicationId, - delete: false - } + name, + applicationId, + delete: false + } const result = await MicroserviceManager.findOne(where, transaction) if (result) { @@ -2553,21 +2565,21 @@ async function _buildGetMicroserviceResponse (microservice, transaction) { // get additional data const portMappings = await MicroservicePortService.getPortMappings(microserviceUuid, transaction) const application = await ApplicationManager.findOne({ id: microservice.applicationId }, transaction) - const extraHosts = await MicroserviceExtraHostManager.findAll({ microserviceUuid: microserviceUuid }, transaction) - const images = await CatalogItemImageManager.findAll({ microserviceUuid: microserviceUuid }, transaction) - const volumeMappings = await VolumeMappingManager.findAll({ microserviceUuid: microserviceUuid }, transaction) - const env = await MicroserviceEnvManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) - const cmd = await MicroserviceArgManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) + const extraHosts = await MicroserviceExtraHostManager.findAll({ microserviceUuid }, transaction) + const images = await CatalogItemImageManager.findAll({ microserviceUuid }, transaction) + const volumeMappings = await VolumeMappingManager.findAll({ microserviceUuid }, transaction) + const env = await MicroserviceEnvManager.findAllExcludeFields({ microserviceUuid }, transaction) + const cmd = await MicroserviceArgManager.findAllExcludeFields({ microserviceUuid }, transaction) const arg = cmd.map((it) => it.cmd) - const cdiDevices = await MicroserviceCdiDevManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) + const cdiDevices = await MicroserviceCdiDevManager.findAllExcludeFields({ microserviceUuid }, transaction) const cdiDevs = cdiDevices.map((it) => it.cdiDevices) - const capAdd = await MicroserviceCapAddManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) + const capAdd = await MicroserviceCapAddManager.findAllExcludeFields({ microserviceUuid }, transaction) const capAdds = capAdd.map((it) => it.capAdd) - const capDrop = await MicroserviceCapDropManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) + const capDrop = await MicroserviceCapDropManager.findAllExcludeFields({ microserviceUuid }, transaction) const capDrops = capDrop.map((it) => it.capDrop) - const status = await MicroserviceStatusManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) - const execStatus = await MicroserviceExecStatusManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) - const healthCheck = await MicroserviceHealthCheckManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) + const status = await MicroserviceStatusManager.findAllExcludeFields({ microserviceUuid }, transaction) + const execStatus = await MicroserviceExecStatusManager.findAllExcludeFields({ microserviceUuid }, transaction) + const healthCheck = await MicroserviceHealthCheckManager.findAllExcludeFields({ microserviceUuid }, transaction) // build microservice response const res = Object.assign({}, microservice) res.ports = [] @@ -2811,8 +2823,8 @@ module.exports = { createVolumeMappingEndPoint: TransactionDecorator.generateTransaction(createVolumeMappingEndPoint), createSystemVolumeMappingEndPoint: TransactionDecorator.generateTransaction(createSystemVolumeMappingEndPoint), deleteMicroserviceEndPoint: TransactionDecorator.generateTransaction(deleteMicroserviceEndPoint, bypassOptions), - deleteMicroserviceWithRoutesAndPortMappings: deleteMicroserviceWithRoutesAndPortMappings, - deleteNotRunningMicroservices: deleteNotRunningMicroservices, + deleteMicroserviceWithRoutesAndPortMappings, + deleteNotRunningMicroservices, deletePortMappingEndPoint: TransactionDecorator.generateTransaction(deletePortMappingEndPoint), deleteSystemPortMappingEndPoint: TransactionDecorator.generateTransaction(deleteSystemPortMappingEndPoint), deleteVolumeMappingEndPoint: TransactionDecorator.generateTransaction(deleteVolumeMappingEndPoint), diff --git a/src/services/nats-api-service.js b/src/services/nats-api-service.js index bc7a8af5..13903b66 100644 --- a/src/services/nats-api-service.js +++ b/src/services/nats-api-service.js @@ -363,7 +363,7 @@ async function getUserCreds (appName, userName, transaction) { if (sysAccount && (sysAccount.isSystem || sysAccount.isLeafSystem)) { accountId = sysAccount.id } - const user = await NatsUserManager.findOne({ accountId: accountId, name: userName }, transaction) + const user = await NatsUserManager.findOne({ accountId, name: userName }, transaction) if (!user) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_NAME, userName)) } diff --git a/src/services/nats-auth-service.js b/src/services/nats-auth-service.js index 515c646a..df49666c 100644 --- a/src/services/nats-auth-service.js +++ b/src/services/nats-auth-service.js @@ -332,8 +332,8 @@ async function rotateOperator (transaction) { const accountRule = account.isSystem ? await NatsAccountRuleManager.findOne({ name: NatsSystemRules.SYSTEM_ACCOUNT_RULE_NAME }, transaction) : (app && app.natsRuleId - ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) - : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) + ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) + : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) const newAccountJwt = await _encodeAccountJwtWithRuleAndRevocations( account.name, accountKp, @@ -551,7 +551,7 @@ async function ensureAccountForApplication (applicationId, transaction) { name: application.name, publicKey: accountKp.getPublicKey(), jwt: accountJwt, - seedSecretName: seedSecretName, + seedSecretName, operatorId: operator.id, applicationId: application.id, isSystem: false, @@ -600,7 +600,7 @@ async function ensureUserForMicroservice (microservice, transaction) { name: userName, publicKey: userKp.getPublicKey(), jwt: userJwt, - credsSecretName: credsSecretName, + credsSecretName, isBearer: false, accountId: account.id, microserviceUuid: microservice.uuid, @@ -686,7 +686,7 @@ async function createMqttBearerUser (applicationId, userName, expiresIn, natsRul name: userName, publicKey: userKp.getPublicKey(), jwt: userJwt, - credsSecretName: credsSecretName, + credsSecretName, isBearer: true, accountId: account.id, microserviceUuid: null, @@ -745,10 +745,10 @@ async function createUserForAccount (accountId, userName, expiresIn, natsRuleNam name: userName, publicKey: userKp.getPublicKey(), jwt: userJwt, - credsSecretName: credsSecretName, + credsSecretName, isBearer: false, accountId: account.id, - microserviceUuid: microserviceUuid, + microserviceUuid, natsUserRuleId: userRule ? userRule.id : null }, transaction) @@ -822,7 +822,7 @@ async function reissueUserForMicroservice (microserviceUuid, transaction, ...res name: microservice.name, publicKey: userKp.getPublicKey(), jwt: userJwt, - credsSecretName: credsSecretName, + credsSecretName, isBearer: false, accountId: account.id, microserviceUuid: microservice.uuid, @@ -855,7 +855,7 @@ async function reissueUserForMicroservice (microserviceUuid, transaction, ...res name: microservice.name, publicKey: userKp.getPublicKey(), jwt: userJwt, - credsSecretName: credsSecretName, + credsSecretName, accountId: account.id, natsUserRuleId: currentRuleId }, transaction) @@ -879,7 +879,7 @@ async function reissueUserForMicroservice (microserviceUuid, transaction, ...res async function ensureLeafUserForAccount (accountId, fogName, transaction, natsInstanceMicroserviceUuid = null) { const leafUserName = `leaf-${fogName}` - const existing = await NatsUserManager.findOne({ accountId: accountId, name: leafUserName }, transaction) + const existing = await NatsUserManager.findOne({ accountId, name: leafUserName }, transaction) if (existing) { if (natsInstanceMicroserviceUuid != null && existing.microserviceUuid !== natsInstanceMicroserviceUuid) { await NatsUserManager.update({ id: existing.id }, { microserviceUuid: natsInstanceMicroserviceUuid }, transaction) @@ -942,8 +942,8 @@ async function _addRevocationToAccount (account, publicKey, transaction) { const accountRule = account.isSystem ? await NatsAccountRuleManager.findOne({ name: NatsSystemRules.SYSTEM_ACCOUNT_RULE_NAME }, transaction) : (app && app.natsRuleId - ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) - : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) + ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) + : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) const revocations = _extractAccountRevocations(account.jwt) revocations[publicKey] = Math.floor(Date.now() / 1000) const accountJwt = await encodeAccount( @@ -965,8 +965,8 @@ async function _reissueOneUserForRule (user, userRuleId, operatorKp, transaction const accountRule = account.isSystem ? await NatsAccountRuleManager.findOne({ name: NatsSystemRules.SYSTEM_ACCOUNT_RULE_NAME }, transaction) : (app && app.natsRuleId - ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) - : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) + ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) + : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) const revocations = _extractAccountRevocations(account.jwt) revocations[user.publicKey] = Math.floor(Date.now() / 1000) const accountJwt = await encodeAccount( @@ -1034,8 +1034,8 @@ async function revokeMicroserviceUser (microserviceUuid, transaction) { const accountRule = account.isSystem ? await NatsAccountRuleManager.findOne({ name: NatsSystemRules.SYSTEM_ACCOUNT_RULE_NAME }, transaction) : (app && app.natsRuleId - ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) - : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) + ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) + : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) const revocations = _extractAccountRevocations(account.jwt) revocations[user.publicKey] = Math.floor(Date.now() / 1000) const accountJwt = await encodeAccount( @@ -1120,8 +1120,8 @@ async function revokeUserByAccountAndName (accountId, userName, transaction) { const accountRule = account.isSystem ? await NatsAccountRuleManager.findOne({ name: NatsSystemRules.SYSTEM_ACCOUNT_RULE_NAME }, transaction) : (app && app.natsRuleId - ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) - : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) + ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) + : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) const revocations = _extractAccountRevocations(account.jwt) revocations[user.publicKey] = Math.floor(Date.now() / 1000) const accountJwt = await encodeAccount( diff --git a/src/services/nats-service.js b/src/services/nats-service.js index 2ddb848e..ff2a0097 100644 --- a/src/services/nats-service.js +++ b/src/services/nats-service.js @@ -249,7 +249,7 @@ async function _ensureNatsCertificates (fog, transaction) { } await CertificateService.createCAEndpoint({ name, - subject: subject, + subject, expiration: 60, type: 'self-signed' }, transaction) @@ -265,7 +265,7 @@ async function _ensureNatsCertificates (fog, transaction) { } await CertificateService.createCertificateEndpoint({ name, - subject: subject, + subject, hosts: hosts.join(','), ca: { type: 'direct', @@ -504,14 +504,16 @@ async function _computeLeafRemotesForInstance (fog, natsInstance, transaction, c if (!upstreamConnections || upstreamConnections.length === 0) { return remotes } - const tlsConfig = certName ? { - ca_file: `${NATS_CERTS_DIR}/${certName}/ca.crt`, - cert_file: `${NATS_CERTS_DIR}/${certName}/tls.crt`, - key_file: `${NATS_CERTS_DIR}/${certName}/tls.key`, - verify: true, - handshake_first: true, - timeout: '3s' - } : undefined + const tlsConfig = certName + ? { + ca_file: `${NATS_CERTS_DIR}/${certName}/ca.crt`, + cert_file: `${NATS_CERTS_DIR}/${certName}/tls.crt`, + key_file: `${NATS_CERTS_DIR}/${certName}/tls.key`, + verify: true, + handshake_first: true, + timeout: '3s' + } + : undefined const appIds = await _getLeafAppIds(fog, transaction) for (const appId of appIds) { const account = await NatsAuthService.ensureAccountForApplication(appId, transaction) @@ -651,7 +653,7 @@ async function _renderAndPersistNatsConfig (fog, natsInstance, certName, mqttCer NATS_SSL_DIR: NATS_CERTS_DIR, NATS_CERT_NAME: certName, NATS_MQTT_CERT_NAME: mqttCertName, - NATS_JWT_DIR: NATS_JWT_DIR, + NATS_JWT_DIR, NATS_JS_MAX_MEMORY_STORE: jsMaxMemory, NATS_JS_MAX_FILE_STORE: jsMaxFile } @@ -750,7 +752,7 @@ async function _ensureNatsMicroservice (fog, mode, transaction) { } const data = { uuid: AppHelper.generateUUID(), - name: name, + name, config: '{}', catalogItemId: catalog.id, iofogUuid: fog.uuid, @@ -977,7 +979,7 @@ async function ensureNatsForFog (fog, natsConfig, transaction) { clusterPort, mqttPort, httpPort, - configMapName: configMapName, + configMapName, jwtDirMountName: natsJwtDirMount(fog), certSecretName: certName, jsStorageSize: jsStorageSize || DEFAULT_JS_STORAGE_SIZE, @@ -1062,7 +1064,7 @@ async function ensureNatsForFog (fog, natsConfig, transaction) { } } - await _ensureVolumeMount(configMapName, { configMapName: configMapName }, transaction) + await _ensureVolumeMount(configMapName, { configMapName }, transaction) await _ensureVolumeMount(natsJwtDirMount(fog), { configMapName: jwtBundleConfigMapName }, transaction) await _ensureVolumeMount(certName, { secretName: certName }, transaction) await _ensureVolumeMount(mqttCertName, { secretName: mqttCertName }, transaction) @@ -1631,7 +1633,7 @@ function normalizeJetstreamSize (value, defaultValue) { module.exports = { ensureNatsForFog: TransactionDecorator.generateTransaction(ensureNatsForFog), reconcileResolverArtifacts: TransactionDecorator.generateTransaction(reconcileResolverArtifacts), - scheduleResolverArtifactsReconcile: scheduleResolverArtifactsReconcile, + scheduleResolverArtifactsReconcile, enqueueReconcileTask: TransactionDecorator.generateTransaction(enqueueReconcileTask), claimNextTask, cleanupNatsForFog: TransactionDecorator.generateTransaction(cleanupNatsForFog), diff --git a/src/services/rbac-service.js b/src/services/rbac-service.js index fc74aae9..1d33f7a9 100644 --- a/src/services/rbac-service.js +++ b/src/services/rbac-service.js @@ -73,7 +73,7 @@ async function _notifyMicroservicesForServiceAccountUpdate (serviceAccount, tran async function listRolesEndpoint (transaction) { const roles = await RbacRoleManager.listRoles(transaction) return { - roles: roles + roles } } @@ -83,7 +83,7 @@ async function getRoleEndpoint (name, transaction) { throw new Errors.NotFoundError(`Role '${name}' not found`) } return { - role: role + role } } @@ -96,7 +96,7 @@ async function createRoleEndpoint (roleData, transaction) { const role = await RbacRoleManager.createRole(roleData, transaction) return { - role: role + role } } @@ -134,7 +134,7 @@ async function updateRoleEndpoint (name, roleData, transaction) { // System roles don't have database IDs, but we already prevent updating system roles above if (roleId != null) { // Find all role bindings that reference this role using roleId for efficient querying - const bindings = await RbacRoleBindingManager.findAll({ roleId: roleId }, transaction) + const bindings = await RbacRoleBindingManager.findAll({ roleId }, transaction) for (const binding of bindings) { // Trigger update to refresh cache and ensure roleId is set await RbacRoleBindingManager.updateRoleBinding(binding.name, { @@ -143,7 +143,7 @@ async function updateRoleEndpoint (name, roleData, transaction) { } // Find all service accounts that reference this role using roleId for efficient querying - const serviceAccounts = await RbacServiceAccountManager.findAll({ roleId: roleId }, transaction) + const serviceAccounts = await RbacServiceAccountManager.findAll({ roleId }, transaction) for (const sa of serviceAccounts) { const application = sa.applicationId ? await ApplicationManager.findOne({ id: sa.applicationId }, transaction) : null const appName = application ? application.name : null @@ -157,7 +157,7 @@ async function updateRoleEndpoint (name, roleData, transaction) { } return { - role: role + role } } @@ -172,7 +172,7 @@ async function deleteRoleEndpoint (name, transaction) { async function listRoleBindingsEndpoint (transaction) { const bindings = await RbacRoleBindingManager.listRoleBindings(transaction) return { - bindings: bindings + bindings } } @@ -182,7 +182,7 @@ async function getRoleBindingEndpoint (name, transaction) { throw new Errors.NotFoundError(`RoleBinding '${name}' not found`) } return { - binding: binding + binding } } @@ -192,7 +192,7 @@ async function createRoleBindingEndpoint (bindingData, transaction) { const binding = await RbacRoleBindingManager.createRoleBinding(bindingData, transaction) return { - binding: binding + binding } } @@ -202,7 +202,7 @@ async function updateRoleBindingEndpoint (name, bindingData, transaction) { const binding = await RbacRoleBindingManager.updateRoleBinding(name, bindingData, transaction) return { - binding: binding + binding } } @@ -217,7 +217,7 @@ async function deleteRoleBindingEndpoint (name, transaction) { async function listServiceAccountsEndpoint (applicationName, transaction) { const serviceAccounts = await RbacServiceAccountManager.listServiceAccounts(transaction, { applicationName }) return { - serviceAccounts: serviceAccounts + serviceAccounts } } diff --git a/src/services/registry-service.js b/src/services/registry-service.js index 7df9a2ab..5362907f 100644 --- a/src/services/registry-service.js +++ b/src/services/registry-service.js @@ -70,7 +70,7 @@ const findRegistries = async function (isCLI, transaction) { const registries = await RegistryManager.findAllWithAttributes(queryRegistry, { exclude: ['password'] }, transaction) return { - registries: registries + registries } } @@ -128,14 +128,14 @@ const updateRegistry = async function (registry, registryId, isCLI, transaction) const where = isCLI ? { - id: registryId - } + id: registryId + } : { - id: registryId - } + id: registryId + } await RegistryManager.update(where, registryUpdate, transaction) - const microservices = await MicroserviceManager.findAllWithStatuses({ registryId: registryId }, transaction) + const microservices = await MicroserviceManager.findAllWithStatuses({ registryId }, transaction) if (microservices.length > 0) { for (const ms of microservices) { await MicroserviceManager.updateAndFind({ uuid: ms.uuid }, { rebuild: true }, transaction) diff --git a/src/services/router-service.js b/src/services/router-service.js index 1b805632..8841c4a0 100644 --- a/src/services/router-service.js +++ b/src/services/router-service.js @@ -106,15 +106,17 @@ async function validateAndReturnUpstreamRouters (upstreamRouterIds, isSystemFog, async function createRouterForFog (fogData, uuid, upstreamRouters, transaction) { const isEdge = fogData.routerMode === 'edge' const messagingPort = fogData.messagingPort || 5671 + const DEFAULT_EDGE_ROUTER_PORT = 45671 + const DEFAULT_INTERIOR_ROUTER_PORT = 55671 // Is default router if we are on a system fog and no other default router already exists const isDefault = (fogData.isSystem) ? !(await RouterManager.findOne({ isDefault: true }, transaction)) : false const routerData = { isEdge, - messagingPort: messagingPort, + messagingPort, host: fogData.host, - edgeRouterPort: !isEdge ? fogData.edgeRouterPort : null, - interRouterPort: !isEdge ? fogData.interRouterPort : null, - isDefault: isDefault, + edgeRouterPort: !isEdge ? fogData.edgeRouterPort || DEFAULT_EDGE_ROUTER_PORT : null, + interRouterPort: !isEdge ? fogData.interRouterPort || DEFAULT_INTERIOR_ROUTER_PORT : null, + isDefault, iofogUuid: uuid } @@ -355,7 +357,7 @@ async function _createRouterMicroservice (isEdge, uuid, microserviceConfig, tran config: JSON.stringify(microserviceConfig), catalogItemId: routerCatalog.id, iofogUuid: uuid, - hostNetworkMode: hostNetworkMode, + hostNetworkMode, isPrivileged: false, logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, schedule: 0, @@ -470,7 +472,7 @@ async function _getRouterMicroserviceConfig (isEdge, uuid, messagingPort, interR siteConfig: { name: uuid, namespace: SITE_CONFIG_NAMESPACE, - platform: platform, + platform, version: SITE_CONFIG_VERSION }, sslProfiles: {} diff --git a/src/services/secret-service.js b/src/services/secret-service.js index 97e3eecc..1aaf5aa8 100644 --- a/src/services/secret-service.js +++ b/src/services/secret-service.js @@ -234,7 +234,7 @@ async function deleteSecretEndpoint (secretName, transaction) { } async function _deleteVolumeMountsUsingSecret (secretName, transaction) { - const volumeMounts = await VolumeMountingManager.findAll({ secretName: secretName }, transaction) + const volumeMounts = await VolumeMountingManager.findAll({ secretName }, transaction) if (volumeMounts.length > 0) { for (const volumeMount of volumeMounts) { await VolumeMountService.deleteVolumeMountEndpoint(volumeMount.name, transaction) @@ -243,12 +243,12 @@ async function _deleteVolumeMountsUsingSecret (secretName, transaction) { } async function _updateChangeTrackingForFogs (secretName, transaction) { - const secretVolumeMounts = await VolumeMountingManager.findAll({ secretName: secretName }, transaction) + const secretVolumeMounts = await VolumeMountingManager.findAll({ secretName }, transaction) if (secretVolumeMounts.length > 0) { for (const secretVolumeMount of secretVolumeMounts) { const volumeMountObj = { name: secretVolumeMount.name, - secretName: secretName + secretName } await VolumeMountService.updateVolumeMountEndpoint(secretVolumeMount.name, volumeMountObj, transaction) } diff --git a/src/services/services-service.js b/src/services/services-service.js index a46b537e..6581d7e6 100644 --- a/src/services/services-service.js +++ b/src/services/services-service.js @@ -45,7 +45,7 @@ function _mapTags (service) { async function _setTags (serviceModel, tagsArray, transaction) { if (tagsArray) { - let tags = [] + const tags = [] for (const tag of tagsArray) { let tagModel = await TagsManager.findOne({ value: tag }, transaction) if (!tagModel) { @@ -222,7 +222,7 @@ async function validateDefaultBridge (serviceConfig, transaction) { } // Get the router for the iofog node - const router = await RouterManager.findOne({ iofogUuid: iofogUuid }, transaction) + const router = await RouterManager.findOne({ iofogUuid }, transaction) if (!router) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_ROUTER, iofogUuid)) } diff --git a/src/services/tunnel-service.js b/src/services/tunnel-service.js index 7c013b26..8dd4efd0 100644 --- a/src/services/tunnel-service.js +++ b/src/services/tunnel-service.js @@ -34,7 +34,7 @@ const openTunnel = async function (tunnelData, isCli, transaction) { tunnel = { username: Config.get('tunnel.username'), password: Config.get('tunnel.password'), - host: host, + host, rsakey: Config.get('tunnel.rsaKey'), lport: Config.get('tunnel.lport'), iofogUuid: iofog.uuid, @@ -64,7 +64,7 @@ const findTunnel = async function (tunnelData, transaction) { const findAll = async function (transaction) { const tunnels = await TunnelManager.findAllWithAttributes({}, { exclude: ['password'] }, transaction) return { - tunnels: tunnels + tunnels } } diff --git a/src/services/volume-mount-service.js b/src/services/volume-mount-service.js index 801bb8b1..2d2763de 100644 --- a/src/services/volume-mount-service.js +++ b/src/services/volume-mount-service.js @@ -34,7 +34,7 @@ async function listVolumeMountsEndpoint (transaction) { async function getVolumeMountEndpoint (name, transaction) { const volumeMount = await VolumeMountingManager.findOne({ - name: name + name }, transaction) if (!volumeMount) { @@ -125,7 +125,7 @@ async function updateVolumeMountEndpoint (name, data, transaction) { configMapName: data.configMapName, secretName: data.secretName } - await VolumeMountingManager.update({ name: name }, updatedVolumeMountObj, transaction) + await VolumeMountingManager.update({ name }, updatedVolumeMountObj, transaction) // Update change tracking for all linked fog nodes await _updateChangeTrackingForFogs(linkedFogUuids, transaction) @@ -138,7 +138,7 @@ async function deleteVolumeMountEndpoint (name, transaction) { const linkedFogUuids = await findVolumeMountedFogNodes(name, transaction) // Delete volume mount - await VolumeMountingManager.delete({ name: name }, transaction) + await VolumeMountingManager.delete({ name }, transaction) // Update change tracking for all linked fog nodes await _updateChangeTrackingForFogs(linkedFogUuids, transaction) diff --git a/src/services/yaml-parser-service.js b/src/services/yaml-parser-service.js index 5177c4d9..cda0c3c9 100644 --- a/src/services/yaml-parser-service.js +++ b/src/services/yaml-parser-service.js @@ -49,7 +49,7 @@ async function parseSecretFile (fileContent, options = {}) { try { const doc = yaml.load(fileContent) if (!doc || !doc.kind) { - throw new Errors.ValidationError(`Invalid YAML format: missing kind field`) + throw new Errors.ValidationError('Invalid YAML format: missing kind field') } if (doc.kind !== 'Secret') { throw new Errors.ValidationError(`Invalid kind ${doc.kind}`) @@ -88,7 +88,7 @@ async function parseVolumeMountFile (fileContent, options = {}) { try { const doc = yaml.load(fileContent) if (!doc || !doc.kind) { - throw new Errors.ValidationError(`Invalid YAML format: missing kind field`) + throw new Errors.ValidationError('Invalid YAML format: missing kind field') } if (doc.kind !== 'VolumeMount') { throw new Errors.ValidationError(`Invalid kind ${doc.kind}`) @@ -136,7 +136,7 @@ async function parseConfigMapFile (fileContent, options = {}) { try { const doc = yaml.load(fileContent) if (!doc || !doc.kind) { - throw new Errors.ValidationError(`Invalid YAML format: missing kind field`) + throw new Errors.ValidationError('Invalid YAML format: missing kind field') } if (doc.kind !== 'ConfigMap') { throw new Errors.ValidationError(`Invalid kind ${doc.kind}`) @@ -185,7 +185,7 @@ async function parseServiceFile (fileContent, options = {}) { try { const doc = yaml.load(fileContent) if (!doc || !doc.kind) { - throw new Errors.ValidationError(`Invalid YAML format: missing kind field`) + throw new Errors.ValidationError('Invalid YAML format: missing kind field') } if (doc.kind !== 'Service') { throw new Errors.ValidationError(`Invalid kind ${doc.kind}`) @@ -277,9 +277,9 @@ const parseMicroserviceYAML = async (microservice) => { } // Check that exactly one of value, valueFromSecret, or valueFromConfigMap is provided - const hasValue = env.hasOwnProperty('value') - const hasValueFromSecret = env.hasOwnProperty('valueFromSecret') - const hasValueFromConfigMap = env.hasOwnProperty('valueFromConfigMap') + const hasValue = Object.hasOwn(env, 'value') + const hasValueFromSecret = Object.hasOwn(env, 'valueFromSecret') + const hasValueFromConfigMap = Object.hasOwn(env, 'valueFromConfigMap') const valueCount = [hasValue, hasValueFromSecret, hasValueFromConfigMap].filter(Boolean).length @@ -473,7 +473,7 @@ async function parseCertificateFile (fileContent) { try { const doc = yaml.load(fileContent) if (!doc || !doc.kind) { - throw new Errors.ValidationError(`Invalid YAML format: missing kind field`) + throw new Errors.ValidationError('Invalid YAML format: missing kind field') } if (doc.kind !== 'Certificate' && doc.kind !== 'CertificateAuthority') { throw new Errors.ValidationError(`Invalid kind ${doc.kind}`) @@ -649,17 +649,17 @@ async function parseNatsUserRuleFile (fileContent, options = {}) { } module.exports = { - parseAppTemplateFile: parseAppTemplateFile, - parseAppFile: parseAppFile, - parseMicroserviceFile: parseMicroserviceFile, - parseSecretFile: parseSecretFile, - parseVolumeMountFile: parseVolumeMountFile, - parseConfigMapFile: parseConfigMapFile, - parseCertificateFile: parseCertificateFile, - parseNatsAccountRuleFile: parseNatsAccountRuleFile, - parseNatsUserRuleFile: parseNatsUserRuleFile, - parseServiceFile: parseServiceFile, - parseRoleFile: parseRoleFile, - parseRoleBindingFile: parseRoleBindingFile, - parseServiceAccountFile: parseServiceAccountFile + parseAppTemplateFile, + parseAppFile, + parseMicroserviceFile, + parseSecretFile, + parseVolumeMountFile, + parseConfigMapFile, + parseCertificateFile, + parseNatsAccountRuleFile, + parseNatsUserRuleFile, + parseServiceFile, + parseRoleFile, + parseRoleBindingFile, + parseServiceAccountFile } diff --git a/src/utils/cert.js b/src/utils/cert.js index c2dccf4f..04b0083a 100644 --- a/src/utils/cert.js +++ b/src/utils/cert.js @@ -1,6 +1,7 @@ const forge = require('node-forge') const k8sClient = require('./k8s-client') const BigNumber = require('bignumber.js') +const logger = require('../logger') // Types for CA input const CA_TYPES = { @@ -90,7 +91,7 @@ async function storeCA (ca, name) { } const secret = { - name: name, + name, type: 'tls', data: secretData } @@ -272,7 +273,7 @@ async function getCAFromK8sSecret (secretName) { // Create CA record await CertificateManager.createCertificateRecord({ name: secretName, - subject: subject, + subject, isCA: true, validFrom: forgeCert.validity.notBefore, validTo: forgeCert.validity.notAfter, @@ -347,167 +348,186 @@ async function generateCertificate ({ isRenewal = false }) { try { - const caCert = await getCAFromInput(ca) + return await _generateCertificateBody({ + name, + subject, + hosts, + expiration, + ca, + isRenewal + }) + } catch (error) { + logger.error(`Certificate generation failed for ${name}:`, error.message) + throw error + } +} - // Generate RSA key pair - const keys = forge.pki.rsa.generateKeyPair(2048) +async function _generateCertificateBody ({ + name, + subject, + hosts, + expiration, + ca, + isRenewal +}) { + const caCert = await getCAFromInput(ca) - // Create a certificate - const cert = forge.pki.createCertificate() + // Generate RSA key pair + const keys = forge.pki.rsa.generateKeyPair(2048) - // Set certificate fields - cert.publicKey = keys.publicKey - cert.serialNumber = generateSerialNumber() + // Create a certificate + const cert = forge.pki.createCertificate() - // Set validity period - const now = new Date() - cert.validity.notBefore = now - cert.validity.notAfter = new Date(now.getTime() + expiration) + // Set certificate fields + cert.publicKey = keys.publicKey + cert.serialNumber = generateSerialNumber() - // Parse the subject string (format: /CN=Subject Name) - const subjectAttrs = [] + // Set validity period + const now = new Date() + cert.validity.notBefore = now + cert.validity.notAfter = new Date(now.getTime() + expiration) - // Extract CN from subject string - let commonName = subject - if (subject.startsWith('/CN=')) { - commonName = subject.substring(4) - } + // Parse the subject string (format: /CN=Subject Name) + const subjectAttrs = [] - subjectAttrs.push({ name: 'commonName', value: commonName }) - cert.setSubject(subjectAttrs) + // Extract CN from subject string + let commonName = subject + if (subject.startsWith('/CN=')) { + commonName = subject.substring(4) + } - // Process hosts for Subject Alternative Names - const hostsList = hosts ? hosts.split(',').map(h => h.trim()) : [] - const altNames = [] - - for (const host of hostsList) { - if (host.match(/^(\d{1,3}\.){3}\d{1,3}$/)) { - // IP address - altNames.push({ type: 7, ip: host }) - altNames.push({ type: 2, value: host }) - } else { - // DNS name - altNames.push({ type: 2, value: host }) - } - } + subjectAttrs.push({ name: 'commonName', value: commonName }) + cert.setSubject(subjectAttrs) + + // Process hosts for Subject Alternative Names + const hostsList = hosts ? hosts.split(',').map(h => h.trim()) : [] + const altNames = [] - // Set up the certificate based on whether we have a CA or not - if (caCert) { - // If we have a CA, use it to sign the certificate - const caForgeCert = forge.pki.certificateFromPem(caCert.certPem || caCert.crtData) - const caForgeKey = forge.pki.privateKeyFromPem(caCert.key) - - // Set the issuer from the CA - cert.setIssuer(caForgeCert.subject.attributes) - - // Add extensions for a server certificate - cert.setExtensions([ - { - name: 'basicConstraints', - cA: false, - critical: true - }, - { - name: 'keyUsage', - digitalSignature: true, - keyEncipherment: true, - critical: true - }, - { - name: 'extKeyUsage', - serverAuth: true, - clientAuth: true - }, - { - name: 'subjectAltName', - altNames: altNames - }, - { - name: 'authorityKeyIdentifier', - authorityCertIssuer: true, - serialNumber: caForgeCert.serialNumber - } - ]) - - // Sign the certificate with the CA's private key - cert.sign(caForgeKey, forge.md.sha256.create()) + for (const host of hostsList) { + if (host.match(/^(\d{1,3}\.){3}\d{1,3}$/)) { + // IP address + altNames.push({ type: 7, ip: host }) + altNames.push({ type: 2, value: host }) } else { - // Self-signed certificate - cert.setIssuer(subjectAttrs) - - // Add extensions for a self-signed server certificate - cert.setExtensions([ - { - name: 'basicConstraints', - cA: false, - critical: true - }, - { - name: 'keyUsage', - digitalSignature: true, - keyEncipherment: true, - critical: true - }, - { - name: 'extKeyUsage', - serverAuth: true, - clientAuth: true - }, - { - name: 'subjectAltName', - altNames: altNames - }, - { - name: 'subjectKeyIdentifier' - } - ]) - - // Self-sign the certificate - cert.sign(keys.privateKey, forge.md.sha256.create()) + // DNS name + altNames.push({ type: 2, value: host }) } + } - // Convert to PEM - const certPem = forge.pki.certificateToPem(cert) - const keyPem = forge.pki.privateKeyToPem(keys.privateKey) + // Set up the certificate based on whether we have a CA or not + if (caCert) { + // If we have a CA, use it to sign the certificate + const caForgeCert = forge.pki.certificateFromPem(caCert.certPem || caCert.crtData) + const caForgeKey = forge.pki.privateKeyFromPem(caCert.key) - // Store the certificate as a TLS secret - const secretData = { - 'tls.crt': Buffer.from(certPem).toString('base64'), - 'tls.key': Buffer.from(keyPem).toString('base64'), - 'ca.crt': Buffer.from(caCert ? caCert.certPem || caCert.crtData : certPem).toString('base64') - } + // Set the issuer from the CA + cert.setIssuer(caForgeCert.subject.attributes) - const secret = { - name: name, - type: 'tls', - data: secretData - } + // Add extensions for a server certificate + cert.setExtensions([ + { + name: 'basicConstraints', + cA: false, + critical: true + }, + { + name: 'keyUsage', + digitalSignature: true, + keyEncipherment: true, + critical: true + }, + { + name: 'extKeyUsage', + serverAuth: true, + clientAuth: true + }, + { + name: 'subjectAltName', + altNames + }, + { + name: 'authorityKeyIdentifier', + authorityCertIssuer: true, + serialNumber: caForgeCert.serialNumber + } + ]) - // Use the secret service to store the certificate - const SecretService = require('../services/secret-service') + // Sign the certificate with the CA's private key + cert.sign(caForgeKey, forge.md.sha256.create()) + } else { + // Self-signed certificate + cert.setIssuer(subjectAttrs) - if (isRenewal) { - // For renewals, delete the existing secret first - try { - await SecretService.deleteSecretEndpoint(name) - } catch (error) { - // If the secret doesn't exist, that's okay, just continue - if (error.name !== 'NotFoundError') { - throw error - } + // Add extensions for a self-signed server certificate + cert.setExtensions([ + { + name: 'basicConstraints', + cA: false, + critical: true + }, + { + name: 'keyUsage', + digitalSignature: true, + keyEncipherment: true, + critical: true + }, + { + name: 'extKeyUsage', + serverAuth: true, + clientAuth: true + }, + { + name: 'subjectAltName', + altNames + }, + { + name: 'subjectKeyIdentifier' } - } + ]) - // Create new secret with certificate data - await SecretService.createSecretEndpoint(secret) + // Self-sign the certificate + cert.sign(keys.privateKey, forge.md.sha256.create()) + } - return { - cert: certPem, - key: keyPem, - ca: caCert ? caCert.crtData : certPem + // Convert to PEM + const certPem = forge.pki.certificateToPem(cert) + const keyPem = forge.pki.privateKeyToPem(keys.privateKey) + + // Store the certificate as a TLS secret + const secretData = { + 'tls.crt': Buffer.from(certPem).toString('base64'), + 'tls.key': Buffer.from(keyPem).toString('base64'), + 'ca.crt': Buffer.from(caCert ? caCert.certPem || caCert.crtData : certPem).toString('base64') + } + + const secret = { + name, + type: 'tls', + data: secretData + } + + // Use the secret service to store the certificate + const SecretService = require('../services/secret-service') + + if (isRenewal) { + // For renewals, delete the existing secret first + try { + await SecretService.deleteSecretEndpoint(name) + } catch (error) { + // If the secret doesn't exist, that's okay, just continue + if (error.name !== 'NotFoundError') { + throw error + } } - } catch (error) { - throw error + } + + // Create new secret with certificate data + await SecretService.createSecretEndpoint(secret) + + return { + cert: certPem, + key: keyPem, + ca: caCert ? caCert.crtData : certPem } } diff --git a/src/vault/aws-secrets-manager-provider.js b/src/vault/aws-secrets-manager-provider.js index 5faa3a71..57be88bc 100644 --- a/src/vault/aws-secrets-manager-provider.js +++ b/src/vault/aws-secrets-manager-provider.js @@ -43,7 +43,7 @@ class AWSSecretsManagerProvider extends BaseVaultProvider { this.client = new SecretsManagerClient({ region: config.region, - credentials: credentials + credentials }) this.GetSecretValueCommand = GetSecretValueCommand @@ -60,13 +60,13 @@ class AWSSecretsManagerProvider extends BaseVaultProvider { } catch (error) { // Provide more specific error messages if (error.code === 'MODULE_NOT_FOUND' || error.message.includes('Cannot find module')) { - throw new Error(`Failed to initialize AWS Secrets Manager: @aws-sdk/client-secrets-manager package is not installed. Please run: npm install @aws-sdk/client-secrets-manager`) + throw new Error('Failed to initialize AWS Secrets Manager: @aws-sdk/client-secrets-manager package is not installed. Please run: npm install @aws-sdk/client-secrets-manager') } if (error.code === 'ENOTFOUND' || error.message.includes('getaddrinfo ENOTFOUND')) { throw new Error(`Failed to connect to AWS Secrets Manager: Invalid region "${config.region}" or network connectivity issue. Please verify the AWS region is correct (e.g., us-east-1, eu-west-1).`) } if (error.name === 'CredentialsProviderError' || error.message.includes('credentials')) { - throw new Error(`Failed to initialize AWS Secrets Manager: Invalid credentials. Please verify AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are correct.`) + throw new Error('Failed to initialize AWS Secrets Manager: Invalid credentials. Please verify AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are correct.') } throw new Error(`Failed to initialize AWS Secrets Manager: ${error.message}`) } diff --git a/src/vault/google-secret-manager-provider.js b/src/vault/google-secret-manager-provider.js index e26dc30b..f5f222a7 100644 --- a/src/vault/google-secret-manager-provider.js +++ b/src/vault/google-secret-manager-provider.js @@ -46,8 +46,8 @@ class GoogleSecretManagerProvider extends BaseVaultProvider { this.client = new SecretManagerServiceClient({ projectId: config.projectId, - keyFilename: keyFilename, - credentials: credentials + keyFilename, + credentials }) this.projectId = config.projectId @@ -83,7 +83,7 @@ class GoogleSecretManagerProvider extends BaseVaultProvider { // Create new secret const [secret] = await this.client.createSecret({ parent: projectPath, - secretId: secretId, + secretId, secret: { replication: { automatic: {} diff --git a/src/vault/hashicorp-vault-provider.js b/src/vault/hashicorp-vault-provider.js index 1724e018..ab6da75b 100644 --- a/src/vault/hashicorp-vault-provider.js +++ b/src/vault/hashicorp-vault-provider.js @@ -107,7 +107,7 @@ class HashiCorpVaultProvider extends BaseVaultProvider { const secretPath = `v1/${this.mount}/data/${vaultPath}` const payload = { - data: data + data } try { diff --git a/src/websocket/log-session-manager.js b/src/websocket/log-session-manager.js index 9998b772..ae033596 100644 --- a/src/websocket/log-session-manager.js +++ b/src/websocket/log-session-manager.js @@ -43,7 +43,7 @@ class LogSessionManager { fogUuid, agent: agentWs, user: userWs, // Single user per session (one-to-one) - tailConfig: tailConfig, // Per-session tail configuration + tailConfig, // Per-session tail configuration lastActivity: Date.now(), createdAt: Date.now(), transaction @@ -91,12 +91,12 @@ class LogSessionManager { try { if (session.microserviceUuid) { await MicroserviceLogStatusManager.delete( - { sessionId: sessionId }, + { sessionId }, transaction ) } else if (session.fogUuid) { await FogLogStatusManager.delete( - { sessionId: sessionId }, + { sessionId }, transaction ) } @@ -128,7 +128,7 @@ class LogSessionManager { logger.error('Error removing log session from database:' + JSON.stringify({ error: error.message, stack: error.stack, - sessionId: sessionId, + sessionId, microserviceUuid: session.microserviceUuid, fogUuid: session.fogUuid })) diff --git a/src/websocket/server.js b/src/websocket/server.js index 878ef95a..27e47a79 100644 --- a/src/websocket/server.js +++ b/src/websocket/server.js @@ -202,7 +202,7 @@ class WebSocketServer { } catch (error) { logger.error('Failed to encode message:' + JSON.stringify({ error: error.message, - message: message + message })) throw new WebSocketError(1008, 'Message encoding failed') } @@ -492,7 +492,6 @@ class WebSocketServer { } catch (error) { logger.error('Error closing WebSocket:', error.message) } - return } catch (error) { logger.error('WebSocket connection error:' + JSON.stringify({ error: error.message, @@ -508,7 +507,7 @@ class WebSocketServer { const microserviceUuid = this.extractMicroserviceUuid(req.url) if (microserviceUuid) { await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: '', status: microserviceExecState.INACTIVE }, transaction ) @@ -537,7 +536,7 @@ class WebSocketServer { async _routeToInternalHandler (ws, req, transaction) { try { // Extract token from headers (already set by protectWebSocket middleware) - let token = req.headers.authorization + const token = req.headers.authorization if (!token) { logger.error('WebSocket internal routing failed: Missing authentication token') try { @@ -627,7 +626,6 @@ class WebSocketServer { } catch (error) { logger.error('Error closing WebSocket:', error.message) } - return } } catch (error) { logger.error('WebSocket internal routing error:' + JSON.stringify({ @@ -735,7 +733,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'agent', - actorId: actorId, + actorId, path: req.url, resourceId: msgMicroserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -748,18 +746,18 @@ class WebSocketServer { this.attachPendingKeepAliveHandler(ws) try { await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: execId, status: microserviceExecState.PENDING }, transaction ) logger.debug('[WS-SESSION] Updated microservice exec status to PENDING', { execId, - microserviceUuid: microserviceUuid + microserviceUuid }) } catch (error) { logger.error('[WS-SESSION] Failed to update microservice exec status to PENDING', { execId, - microserviceUuid: microserviceUuid, + microserviceUuid, error: error.message, stack: error.stack }) @@ -810,7 +808,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'agent', - actorId: actorId, + actorId, path: req.url, resourceId: msgMicroserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -849,7 +847,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'agent', - actorId: actorId, + actorId, path: req.url, resourceId: msgMicroserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -897,7 +895,7 @@ class WebSocketServer { await EventService.createWsDisconnectEvent({ timestamp: Date.now(), endpointType: 'agent', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null, @@ -917,7 +915,7 @@ class WebSocketServer { try { const closeMsg = { type: MESSAGE_TYPES.CLOSE, - execId: execId, + execId, microserviceUuid: session.microserviceUuid, timestamp: Date.now(), data: Buffer.from('Agent closed connection') @@ -970,7 +968,7 @@ class WebSocketServer { try { const pendingExecStatus = await MicroserviceExecStatusManager.findAllExcludeFields( { - microserviceUuid: microserviceUuid, + microserviceUuid, status: microserviceExecState.PENDING }, transaction @@ -1047,7 +1045,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -1067,7 +1065,7 @@ class WebSocketServer { }) this.sessionManager.createSession(availableExecId, microserviceUuid, null, ws, transaction) await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: availableExecId, status: microserviceExecState.ACTIVE }, transaction ) @@ -1089,7 +1087,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -1148,7 +1146,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -1169,7 +1167,7 @@ class WebSocketServer { this.sessionManager.removePendingUser(microserviceUuid, ws) this.sessionManager.createSession(availableExecId, microserviceUuid, null, ws, transaction) await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: availableExecId, status: microserviceExecState.ACTIVE }, transaction ) @@ -1190,7 +1188,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -1211,7 +1209,7 @@ class WebSocketServer { const statusMsg = { type: MESSAGE_TYPES.STDERR, data: Buffer.from('Waiting for agent connection. Please ensure the microservice/agent is running.\n'), - microserviceUuid: microserviceUuid, + microserviceUuid, execId: 'pending', // Since we don't have execSessionId anymore timestamp: Date.now() } @@ -1280,7 +1278,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -1290,7 +1288,7 @@ class WebSocketServer { } }) clearInterval(retryTimer) // Stop retry timer - return // Exit early, session activated successfully + // Exit early, session activated successfully } } else { // Agent is on different replica - activate with user only @@ -1302,7 +1300,7 @@ class WebSocketServer { this.sessionManager.removePendingUser(microserviceUuid, ws) this.sessionManager.createSession(availableExecId, microserviceUuid, null, ws, transaction) await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: availableExecId, status: microserviceExecState.ACTIVE }, transaction ) @@ -1323,7 +1321,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -1333,7 +1331,6 @@ class WebSocketServer { } }) clearInterval(retryTimer) // Stop retry timer - return } } } catch (retryError) { @@ -1365,7 +1362,7 @@ class WebSocketServer { const timeoutMsg = { type: MESSAGE_TYPES.STDERR, data: Buffer.from('Timeout waiting for agent connection. Please try again.\n'), - microserviceUuid: microserviceUuid, + microserviceUuid, execId: 'pending', // Since we don't have execSessionId anymore timestamp: Date.now() } @@ -1419,7 +1416,7 @@ class WebSocketServer { await EventService.createWsDisconnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null, @@ -1527,12 +1524,12 @@ class WebSocketServer { const activationMsg = { type: MESSAGE_TYPES.ACTIVATION, data: Buffer.from(JSON.stringify({ - execId: execId, + execId, microserviceUuid: session.microserviceUuid, timestamp: Date.now() })), microserviceUuid: session.microserviceUuid, - execId: execId, + execId, timestamp: Date.now() } @@ -1612,7 +1609,7 @@ class WebSocketServer { type: MESSAGE_TYPES.STDIN, data: Buffer.from(text + '\n'), // Add newline for command execution microserviceUuid: session.microserviceUuid, - execId: execId, + execId, timestamp: Date.now() } @@ -1708,7 +1705,7 @@ class WebSocketServer { type: MESSAGE_TYPES.CONTROL, data: Buffer.from('keepalive'), microserviceUuid: session.microserviceUuid, - execId: execId, + execId, timestamp: Date.now() } const encoded = this.encodeMessage(keepAliveResponse) @@ -1818,7 +1815,7 @@ class WebSocketServer { type: msg.type, data: msg.data, microserviceUuid: session.microserviceUuid, - execId: execId, + execId, timestamp: Date.now() } // Encode and send as binary @@ -1980,7 +1977,7 @@ class WebSocketServer { // 3. Check microservice status const statusArr = await MicroserviceStatusManager.findAllExcludeFields({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) if (!statusArr || statusArr.length === 0) { throw new Errors.NotFoundError('Microservice status not found') @@ -2074,7 +2071,7 @@ class WebSocketServer { if (session.agent && session.agent.readyState === WebSocket.OPEN) { const closeMsg = { type: MESSAGE_TYPES.CLOSE, - execId: execId, + execId, microserviceUuid: session.microserviceUuid, timestamp: Date.now(), data: Buffer.from('Session closed') @@ -2428,7 +2425,7 @@ class WebSocketServer { await this.validateMicroservice(microserviceUuid, expectSystem, transaction) const statusArr = await MicroserviceStatusManager.findAllExcludeFields({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) if (!statusArr || statusArr.length === 0) { throw new Errors.NotFoundError('Microservice status not found') @@ -2497,9 +2494,9 @@ class WebSocketServer { // 4. Create log session in database (no HTTP POST needed!) if (microserviceUuid) { await MicroserviceLogStatusManager.create({ - microserviceUuid: microserviceUuid, - logSessionId: logSessionId, - sessionId: sessionId, // Unique per user session + microserviceUuid, + logSessionId, + sessionId, // Unique per user session status: 'PENDING', tailConfig: JSON.stringify(tailConfig), agentConnected: false, @@ -2508,8 +2505,8 @@ class WebSocketServer { } else if (fogUuid) { await FogLogStatusManager.create({ iofogUuid: fogUuid, - logSessionId: logSessionId, - sessionId: sessionId, // Unique per user session + logSessionId, + sessionId, // Unique per user session status: 'PENDING', tailConfig: JSON.stringify(tailConfig), agentConnected: false, @@ -2567,10 +2564,10 @@ class WebSocketServer { const sessionInfoMsg = { type: MESSAGE_TYPES.LOG_START, data: Buffer.from(JSON.stringify({ - sessionId: sessionId, - tailConfig: tailConfig + sessionId, + tailConfig })), - sessionId: sessionId, + sessionId, timestamp: Date.now() } ws.send(this.encodeMessage(sessionInfoMsg), { binary: true }) @@ -2580,7 +2577,7 @@ class WebSocketServer { const waitingMsg = { type: MESSAGE_TYPES.LOG_LINE, data: Buffer.from('Waiting for agent connection. Log streaming will begin once the agent connects.\n'), - sessionId: sessionId, + sessionId, timestamp: Date.now(), microserviceUuid: microserviceUuid || null, iofogUuid: fogUuid || null @@ -2612,7 +2609,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid || fogUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -2632,13 +2629,13 @@ class WebSocketServer { // Update database if (microserviceUuid) { await MicroserviceLogStatusManager.update( - { sessionId: sessionId }, + { sessionId }, { userConnected: false }, transaction ) } else if (fogUuid) { await FogLogStatusManager.update( - { sessionId: sessionId }, + { sessionId }, { userConnected: false }, transaction ) @@ -2671,7 +2668,7 @@ class WebSocketServer { await EventService.createWsDisconnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid || fogUuid, ipAddress: EventService.extractIPv4Address(req) || null, @@ -2701,12 +2698,12 @@ class WebSocketServer { let logStatus = null if (microserviceUuid) { logStatus = await MicroserviceLogStatusManager.findOne( - { sessionId: sessionId }, + { sessionId }, transaction ) } else if (iofogUuid) { logStatus = await FogLogStatusManager.findOne( - { sessionId: sessionId }, + { sessionId }, transaction ) } @@ -2735,13 +2732,13 @@ class WebSocketServer { // 4. Update database if (microserviceUuid) { await MicroserviceLogStatusManager.update( - { sessionId: sessionId }, + { sessionId }, { agentConnected: true, status: 'ACTIVE' }, transaction ) } else if (iofogUuid) { await FogLogStatusManager.update( - { sessionId: sessionId }, + { sessionId }, { agentConnected: true, status: 'ACTIVE' }, transaction ) @@ -2818,10 +2815,10 @@ class WebSocketServer { const configMsg = { type: MESSAGE_TYPES.LOG_START, data: Buffer.from(JSON.stringify({ - sessionId: sessionId, - tailConfig: tailConfig + sessionId, + tailConfig })), - sessionId: sessionId, + sessionId, timestamp: Date.now() } ws.send(this.encodeMessage(configMsg), { binary: true }) @@ -2832,10 +2829,10 @@ class WebSocketServer { const agentConnectedMsg = { type: MESSAGE_TYPES.LOG_START, data: Buffer.from(JSON.stringify({ - sessionId: sessionId, + sessionId, message: 'Agent connected. Log streaming started.\n' })), - sessionId: sessionId, + sessionId, timestamp: Date.now() } session.user.send(this.encodeMessage(agentConnectedMsg), { binary: true }) @@ -2878,7 +2875,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'agent', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid || iofogUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -2898,13 +2895,13 @@ class WebSocketServer { // Update database if (microserviceUuid) { await MicroserviceLogStatusManager.update( - { sessionId: sessionId }, + { sessionId }, { agentConnected: false }, transaction ) } else if (iofogUuid) { await FogLogStatusManager.update( - { sessionId: sessionId }, + { sessionId }, { agentConnected: false }, transaction ) @@ -2949,7 +2946,7 @@ class WebSocketServer { await EventService.createWsDisconnectEvent({ timestamp: Date.now(), endpointType: 'agent', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid || iofogUuid, ipAddress: EventService.extractIPv4Address(req) || null, diff --git a/src/websocket/session-manager.js b/src/websocket/session-manager.js index cc92bf14..1b5b12be 100644 --- a/src/websocket/session-manager.js +++ b/src/websocket/session-manager.js @@ -200,7 +200,7 @@ class SessionManager { agentState: newConnection.readyState })) await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: execId, status: microserviceExecState.ACTIVE }, transaction ) @@ -227,7 +227,7 @@ class SessionManager { agentState: pendingAgent.readyState })) await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: execId, status: microserviceExecState.ACTIVE }, transaction ) diff --git a/swagger.js b/swagger.js index 4240fdbb..3bf69317 100644 --- a/swagger.js +++ b/swagger.js @@ -26,7 +26,7 @@ const swaggerOptions = { description: 'JWT token for authentication (user or agent)' } }, - schemas: schemas + schemas }, security: [ { diff --git a/test/src/services/iofog-service.test.js b/test/src/services/iofog-service.test.js index c86f6345..90ad224f 100644 --- a/test/src/services/iofog-service.test.js +++ b/test/src/services/iofog-service.test.js @@ -199,6 +199,7 @@ describe('ioFog Service', () => { $sandbox.stub(NatsService, 'ensureNatsForFog').returns(Promise.resolve()) $sandbox.stub(ioFogManager, 'update').returns($createIoFogResponse) $sandbox.stub(ioFogManager, 'findOne').returns(Promise.resolve()) + $sandbox.stub(ioFogManager, 'findAll').returns(Promise.resolve([{ uuid: 'existing-fog' }])) $sandbox.stub(ioFogManager, 'findOneWithTags').returns(Promise.resolve()) $sandbox.stub(Date, 'now').returns($dateResponse) From d17c635b9f35b8a43676c4d2cf5a2345fb34e0b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 02:17:40 +0300 Subject: [PATCH 58/75] Replace ECN Viewer with EdgeOps Console static embed. Serve the console from EDGEOPS_CONSOLE_PATH or a Docker-built static bundle, rename viewer env and config keys to console, and add local dev scripts to build and run the embedded UI. --- .env.example | 21 +++++++ .gitignore | 3 + Dockerfile | 40 ++++++++++++- docs/external-oidc-client-setup.md | 12 ++-- docs/swagger.yaml | 32 +++++------ package-lock.json | 7 --- package.json | 9 ++- scripts/build-console-dev.js | 83 +++++++++++++++++++++++++++ scripts/init.js | 2 +- scripts/start.js | 6 +- scripts/stop.js | 2 +- src/config/auth-urls.js | 6 +- src/config/config.yaml | 20 +++---- src/config/embedded-oidc.js | 40 ++++++------- src/config/env-mapping.js | 10 ++-- src/main.js | 10 ++-- src/routes/user.js | 6 +- src/server.js | 91 ++++++++++++++++++------------ src/services/auth-oauth-service.js | 15 ++--- src/services/controller-service.js | 30 +++++++++- 20 files changed, 317 insertions(+), 128 deletions(-) create mode 100644 .env.example create mode 100644 scripts/build-console-dev.js diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..61c800e5 --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +# Copy to .env and adjust for local embedded dev (npm run dev:embedded). + +NODE_ENV=development + +# EdgeOps Console static embed (npm run build:console → dev/console/build) +EDGEOPS_CONSOLE_PATH=dev/console/build # must be absolute path +EDGEOPS_CONSOLE_VERSION=1.0.0 +# EDGEOPS_CONSOLE_REPO=https://github.com/Datasance/edgeops-console +# EDGEOPS_CONSOLE_FLAVOR=datasance + +CONSOLE_PORT=8008 +CONTROLLER_PUBLIC_URL=http://localhost:51121 +CONSOLE_URL=http://localhost:8008 + +# Avoid sudo for file logging during local dev +LOG_DIRECTORY=dev/logs + +# Embedded OIDC (optional — see docs/external-oidc-client-setup.md) +# AUTH_MODE=embedded +# OIDC_BOOTSTRAP_ADMIN_USERNAME=admin +# OIDC_BOOTSTRAP_ADMIN_PASSWORD=changeme diff --git a/.gitignore b/.gitignore index b9ca7acb..e6efdf24 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ diagnostic/ iofog-iofogcontroller-*.tgz .npmrc .env +dev/edgeops-console/ +dev/console/ +dev/logs/ src/iofog-controller.pid mcp-controller-server/ .cursor/ diff --git a/Dockerfile b/Dockerfile index 023932a6..58193f48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,32 @@ +# Stage 1 — EdgeOps Console static SPA (Plan 11-1) +# ioFog overrides: EDGEOPS_CONSOLE_REPO=https://github.com/eclipse-iofog/edgeops-console +# EDGEOPS_CONSOLE_FLAVOR=iofog +FROM node:24-bookworm AS console-builder + +ARG EDGEOPS_CONSOLE_REPO=https://github.com/Datasance/edgeops-console +ARG EDGEOPS_CONSOLE_VERSION=1.0.0 +ARG EDGEOPS_CONSOLE_FLAVOR=datasance + +RUN apt-get update \ + && apt-get install -y --no-install-recommends git \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /tmp/console-src + +RUN VERSION="${EDGEOPS_CONSOLE_VERSION}" \ + && if [ "${VERSION#v}" = "${VERSION}" ]; then VERSION="v${VERSION}"; fi \ + && git clone --depth 1 --branch "${VERSION}" "${EDGEOPS_CONSOLE_REPO}" . + +RUN npm ci --legacy-peer-deps + +RUN VITE_DISTRIBUTION="${EDGEOPS_CONSOLE_FLAVOR}" sh package.sh + +RUN test -f build/index.html \ + && test -d build/assets \ + && mkdir -p /tmp/console \ + && cp -a build /tmp/console/build + + FROM node:24-bookworm AS builder ARG PKG_VERSION @@ -20,6 +49,8 @@ RUN npm pack FROM registry.access.redhat.com/ubi9/nodejs-24-minimal:latest +ARG EDGEOPS_CONSOLE_VERSION=1.0.0 + USER root # Install dependencies for logging and development RUN microdnf install -y g++ make && microdnf clean all @@ -37,6 +68,11 @@ RUN useradd --uid 10000 --create-home runner RUN mkdir -p /var/log/iofog-controller && \ chown runner:runner /var/log/iofog-controller && \ chmod 755 /var/log/iofog-controller + +COPY --from=console-builder /tmp/console/build /home/runner/static/console +RUN echo "${EDGEOPS_CONSOLE_VERSION}" > /home/runner/static/console/VERSION \ + && chown -R runner:runner /home/runner/static/console + USER 10000 WORKDIR /home/runner @@ -46,7 +82,9 @@ ENV PATH=$PATH:/home/runner/.npm-global/bin COPY --from=builder /tmp/datasance-iofogcontroller-*.tgz /home/runner/iofog-controller.tgz -ENV PID_BASE=/home/runner +ENV PID_BASE=/home/runner +ENV EDGEOPS_CONSOLE_PATH=/home/runner/static/console +ENV EDGEOPS_CONSOLE_VERSION=${EDGEOPS_CONSOLE_VERSION} RUN npm i -g /home/runner/iofog-controller.tgz && \ rm -rf /home/runner/iofog-controller.tgz && \ diff --git a/docs/external-oidc-client-setup.md b/docs/external-oidc-client-setup.md index 1691b994..4e75ea4d 100644 --- a/docs/external-oidc-client-setup.md +++ b/docs/external-oidc-client-setup.md @@ -10,7 +10,7 @@ Controller uses one **confidential** OIDC client for browser and CLI authenticat | Use case | Grant / flow | Controller endpoint | |----------|--------------|---------------------| -| Browser (ECN Viewer) | Authorization code + PKCE S256 | `GET /api/v3/user/oauth/authorize` → IdP → `GET /api/v3/user/oauth/callback` | +| Browser (EdgeOps Console) | Authorization code + PKCE S256 | `GET /api/v3/user/oauth/authorize` → IdP → `GET /api/v3/user/oauth/callback` | | CLI (potctl) | Resource owner password (direct access) | `POST /api/v3/user/login` | | Session refresh | Refresh token | `POST /api/v3/user/refresh` | | Profile | Bearer access token (+ UserInfo) | `GET /api/v3/user/profile` | @@ -26,7 +26,7 @@ In external mode, access and refresh tokens are **issued by the IdP**. Controlle | `OIDC_CLIENT_ID` | Yes | `pot-controller` | | `OIDC_CLIENT_SECRET` | Yes | Confidential client secret | | `CONTROLLER_PUBLIC_URL` | Yes | `https://controller.example.com` | -| `VIEWER_URL` | Yes (browser login) | `https://viewer.example.com` | +| `CONSOLE_URL` | Yes (browser login) | `https://console.example.com` | | `AUTH_INSECURE_ALLOW_HTTP` | Development only | `true` when using `http://localhost:*` | ## IdP client — required settings @@ -73,10 +73,10 @@ Avoid overly broad wildcards in production. ### Web origins (CORS) -If the Viewer calls the Controller API from the browser, allow: +If the Console calls the Controller API from the browser, allow: ```text -{VIEWER_URL} +{CONSOLE_URL} ``` Example: `http://localhost:3000` @@ -160,7 +160,7 @@ Example client: `pot-controller` | Direct access grants | On (if CLI login is required) | | PKCE Method | S256 | | Valid redirect URIs | `{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback` | -| Web origins | `{VIEWER_URL}` | +| Web origins | `{CONSOLE_URL}` | | Client scopes | `openid`, `profile`, `email`, `roles` (default); `groups`, `offline_access` (optional) | ### MFA and forced password change @@ -174,7 +174,7 @@ Example client: `pot-controller` ### Browser 1. Viewer Sign in → IdP login page (no `invalid_scope` or PKCE errors) -2. Callback → `{VIEWER_URL}/login#accessToken=...&refreshToken=...` +2. Callback → `{CONSOLE_URL}/login#accessToken=...&refreshToken=...` 3. `GET /api/v3/user/profile` with `Authorization: Bearer ` → 200 ### CLI diff --git a/docs/swagger.yaml b/docs/swagger.yaml index b1b3f923..998b1fc4 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,7 +1,7 @@ openapi : "3.0.0" info: - version: 3.7.0 - title: Datasance PoT Controller + version: 3.8.0 + title: Edge Compute Network Controller paths: /status: get: @@ -3254,27 +3254,27 @@ paths: summary: Start OAuth authorization (BFF) operationId: oauthAuthorize description: > - Browser OAuth BFF entry point for ECN Viewer (Plan 8.2). ECN Viewer Sign in performs a + Browser OAuth BFF entry point for EdgeOps Console (Plan 8.2). EdgeOps Console Sign in performs a full-page redirect here; do **not** POST credentials from the browser. **External mode:** redirects to the external IdP authorize endpoint. **Embedded mode:** redirects to the in-process issuer at `{CONTROLLER_PUBLIC_URL}/oidc/auth`, which starts an OAuth interaction and redirects the browser to - `{viewerUrl}{auth.oauthInteractionUrl}?interaction=` for Viewer-owned login/MFA steps. + `{consoleUrl}{auth.oauthInteractionUrl}?interaction=` for Console-owned login/MFA steps. Redirect chain (external): - `Viewer → GET /user/oauth/authorize → external IdP (login, MFA, password policy) → - GET /user/oauth/callback → 302 {viewerUrl}/login#accessToken=...&refreshToken=...` + `Console → GET /user/oauth/authorize → external IdP (login, MFA, password policy) → + GET /user/oauth/callback → 302 {consoleUrl}/login#accessToken=...&refreshToken=...` Redirect chain (embedded): - `Viewer → GET /user/oauth/authorize → /oidc/auth → {viewerUrl}/login/oauth?interaction= - → interaction APIs → GET /user/oauth/callback → 302 {viewerUrl}/login#tokens` + `Console → GET /user/oauth/authorize → /oidc/auth → {consoleUrl}/login/oauth?interaction= + → interaction APIs → GET /user/oauth/callback → 302 {consoleUrl}/login#tokens` OAuth callback redirect URI: `{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback`. - Requires `VIEWER_URL` in embedded mode. External mode requires `OIDC_ISSUER_URL`, + Requires `CONSOLE_URL` in embedded mode. External mode requires `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, and `OIDC_CLIENT_SECRET` for the confidential Controller client. responses: "302": @@ -3282,7 +3282,7 @@ paths: "401": description: Auth not configured or OAuth session error "501": - description: Embedded OAuth BFF requires VIEWER_URL + description: Embedded OAuth BFF requires CONSOLE_URL "429": $ref: "#/components/responses/AuthRateLimitExceeded" /user/oauth/callback: @@ -3296,9 +3296,9 @@ paths: Validates `state` and `nonce` from the server session (10-minute TTL). Works in **embedded** and **external** auth modes. - When `VIEWER_URL` is configured (recommended for browser login), responds with **302** to - `{viewerUrl}/login#accessToken=...&refreshToken=...`. Viewer `/login` parses the URL - fragment, stores tokens, clears the hash, and navigates to the app. When `VIEWER_URL` is + When `CONSOLE_URL` is configured (recommended for browser login), responds with **302** to + `{consoleUrl}/login#accessToken=...&refreshToken=...`. EdgeOps Console `/login` parses the URL + fragment, stores tokens, clears the hash, and navigates to the app. When `CONSOLE_URL` is unset, returns JSON **200** `{ accessToken, refreshToken }` (CLI/automation only). **External forced password change** is enforced by the IdP during authorize (e.g. Keycloak @@ -3308,17 +3308,17 @@ paths: MFA in external browser login is IdP-owned; embedded browser MFA uses interaction endpoints. responses: "200": - description: Token response when VIEWER_URL is not configured + description: Token response when CONSOLE_URL is not configured content: application/json: schema: $ref: "#/components/schemas/LoginSuccessResponse" "302": - description: Redirect to ECN Viewer `/login` with tokens in URL fragment + description: Redirect to EdgeOps Console `/login` with tokens in URL fragment "401": description: OAuth session expired, state/nonce mismatch, or authorization code exchange failed "501": - description: Embedded OAuth BFF requires VIEWER_URL + description: Embedded OAuth BFF requires CONSOLE_URL "429": $ref: "#/components/responses/AuthRateLimitExceeded" /user/interaction/{uid}: diff --git a/package-lock.json b/package-lock.json index e47273b5..e1b905c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "@aws-sdk/client-secrets-manager": "^3.1042.0", "@azure/identity": "^4.13.1", "@azure/keyvault-secrets": "^4.10.0", - "@datasance/ecn-viewer": "1.4.4", "@google-cloud/secret-manager": "^6.1.1", "@kubernetes/client-node": "^1.4.0", "@msgpack/msgpack": "^3.1.2", @@ -1019,12 +1018,6 @@ "node": ">=6.9.0" } }, - "node_modules/@datasance/ecn-viewer": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@datasance/ecn-viewer/-/ecn-viewer-1.4.4.tgz", - "integrity": "sha512-F3MC2R/OTC+ivbsEwZw0813wdm9tXKdjw7mcxKl2cc0+9k4dptQOInMMpd8+poAKTJYATOQyyeXUVaLJPZRNUA==", - "license": "EPL-2.0" - }, "node_modules/@emnapi/core": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz", diff --git a/package.json b/package.json index e4a473cd..b24321e4 100644 --- a/package.json +++ b/package.json @@ -45,14 +45,18 @@ "snyk": "./node_modules/.bin/snyk monitor", "pretest": "npm run lint", "test": "node scripts/run-test.js test", - "test:k8s-client": "node scripts/run-test.js test --grep 'k8s-client'", + "test:all": "node scripts/run-test.js test-all", + "test:k8s-client": "node scripts/run-test.js test test/integration/k8s-client-integration.test.js", "precli-tests": "npm run lint", "cli-tests": "node scripts/run-test.js cli-tests", "precoverage": "npm run lint", "coverage": "node scripts/run-test.js coverage", "prepare": "npm run lint", "swagger": "./generate-swagger.sh", - "rbac-audit": "node scripts/rbac-audit.js" + "rbac-audit": "node scripts/rbac-audit.js", + "build:console": "node scripts/build-console-dev.js", + "start:server": "sudo -E node -r dotenv/config src/server.js", + "dev:embedded": "npm run build:console && npm run start:server" }, "preferGlobal": true, "bin": { @@ -62,7 +66,6 @@ "@aws-sdk/client-secrets-manager": "^3.1042.0", "@azure/identity": "^4.13.1", "@azure/keyvault-secrets": "^4.10.0", - "@datasance/ecn-viewer": "1.4.4", "@google-cloud/secret-manager": "^6.1.1", "@kubernetes/client-node": "^1.4.0", "@msgpack/msgpack": "^3.1.2", diff --git a/scripts/build-console-dev.js b/scripts/build-console-dev.js new file mode 100644 index 00000000..5f6d9ebe --- /dev/null +++ b/scripts/build-console-dev.js @@ -0,0 +1,83 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const { execSync } = require('child_process') +const fs = require('fs') +const path = require('path') + +const ROOT = path.join(__dirname, '..') +const DEV_DIR = path.join(ROOT, 'dev') +const CLONE_DIR = path.join(DEV_DIR, 'edgeops-console') +const CONSOLE_DIR = path.join(DEV_DIR, 'console') +const BUILD_OUT = path.join(CONSOLE_DIR, 'build') + +const REPO = process.env.EDGEOPS_CONSOLE_REPO || 'https://github.com/Datasance/edgeops-console' +const VERSION = process.env.EDGEOPS_CONSOLE_VERSION || '1.0.0' +const FLAVOR = process.env.EDGEOPS_CONSOLE_FLAVOR || 'datasance' + +function normalizeTag (version) { + return version.startsWith('v') ? version : `v${version}` +} + +function run (command, options = {}) { + execSync(command, { + stdio: 'inherit', + cwd: options.cwd || ROOT, + env: options.env || process.env + }) +} + +function ensureConsoleSource () { + const tag = normalizeTag(VERSION) + + if (fs.existsSync(path.join(CLONE_DIR, '.git'))) { + try { + run(`git fetch origin --depth 1 tag ${tag}`, { cwd: CLONE_DIR }) + run(`git checkout ${tag}`, { cwd: CLONE_DIR }) + return + } catch (_) { + fs.rmSync(CLONE_DIR, { recursive: true, force: true }) + } + } + + fs.mkdirSync(DEV_DIR, { recursive: true }) + run(`git clone --depth 1 --branch ${tag} ${REPO} ${CLONE_DIR}`) +} + +function copyBuildOutput () { + const srcBuild = path.join(CLONE_DIR, 'build') + if (!fs.existsSync(path.join(srcBuild, 'index.html'))) { + throw new Error('Console build failed: build/index.html missing') + } + if (!fs.existsSync(path.join(srcBuild, 'assets'))) { + throw new Error('Console build failed: build/assets missing') + } + + fs.rmSync(BUILD_OUT, { recursive: true, force: true }) + fs.mkdirSync(CONSOLE_DIR, { recursive: true }) + fs.cpSync(srcBuild, BUILD_OUT, { recursive: true }) + fs.writeFileSync(path.join(CONSOLE_DIR, 'VERSION'), `${VERSION.replace(/^v/, '')}\n`) +} + +function buildConsoleDev () { + ensureConsoleSource() + run('npm ci --legacy-peer-deps', { cwd: CLONE_DIR }) + run('sh package.sh', { + cwd: CLONE_DIR, + env: { ...process.env, VITE_DISTRIBUTION: FLAVOR } + }) + copyBuildOutput() + console.log(`EdgeOps Console built at ${BUILD_OUT}`) +} + +buildConsoleDev() diff --git a/scripts/init.js b/scripts/init.js index 10c51f5e..48fe4291 100644 --- a/scripts/init.js +++ b/scripts/init.js @@ -19,7 +19,7 @@ function init () { const options = { env: { NODE_ENV: 'production', - VIEWER_PORT: '8008', + CONSOLE_PORT: '8008', PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] diff --git a/scripts/start.js b/scripts/start.js index e3db44a7..d4a391d6 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -19,7 +19,7 @@ function start () { const options = { env: { NODE_ENV: 'production', - VIEWER_PORT: '8008', + CONSOLE_PORT: '8008', PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] @@ -27,8 +27,8 @@ function start () { options.env = setDbEnvVars(options.env) - if (process.env.VIEWER_PORT) { - options.env.VIEWER_PORT = process.env.VIEWER_PORT + if (process.env.CONSOLE_PORT) { + options.env.CONSOLE_PORT = process.env.CONSOLE_PORT } execSync('node ./src/main.js start', options) diff --git a/scripts/stop.js b/scripts/stop.js index 18c7b3a1..316fb647 100644 --- a/scripts/stop.js +++ b/scripts/stop.js @@ -19,7 +19,7 @@ function stop () { const options = { env: { NODE_ENV: 'production', - VIEWER_PORT: '8008', + CONSOLE_PORT: '8008', PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] diff --git a/src/config/auth-urls.js b/src/config/auth-urls.js index cac7d66e..adbcecf9 100644 --- a/src/config/auth-urls.js +++ b/src/config/auth-urls.js @@ -10,8 +10,8 @@ function getPublicUrl () { return normalizeUrl(process.env.CONTROLLER_PUBLIC_URL || config.get('server.publicUrl') || '') } -function getViewerUrl () { - const explicit = process.env.VIEWER_URL || config.get('viewer.url') +function getConsoleUrl () { + const explicit = process.env.CONSOLE_URL || config.get('console.url') if (explicit) { return normalizeUrl(explicit) } @@ -20,6 +20,6 @@ function getViewerUrl () { module.exports = { getPublicUrl, - getViewerUrl, + getConsoleUrl, normalizeUrl } diff --git a/src/config/config.yaml b/src/config/config.yaml index 478ffbcd..ddb8d966 100644 --- a/src/config/config.yaml +++ b/src/config/config.yaml @@ -37,10 +37,10 @@ server: # cert: # TLS certificate in base64 format (TLS_BASE64_CERT) # intermediateCert: # Intermediate certificate in base64 format (TLS_BASE64_INTERMEDIATE_CERT) -# Viewer Configuration -viewer: - port: 8008 # Viewer port number - url: "" # Viewer URL (VIEWER_URL); defaults to server.publicUrl when empty +# Console Configuration +console: + port: 8008 # Console port number + url: "http://localhost:8008" # Console URL (CONSOLE_URL); defaults to server.publicUrl when empty # Logging Configuration log: @@ -96,18 +96,18 @@ database: idle: 20000 # Idle timeout in milliseconds # Auth Configuration (OIDC — any compliant provider; env: OIDC_* / AUTH_* per naming-map §13) -# auth: -# mode: embedded # embedded | external (AUTH_MODE); resolution -# insecureAllowHttp: false # Allow http CONTROLLER_PUBLIC_URL in prod (AUTH_INSECURE_ALLOW_HTTP) -# bootstrap: +auth: + mode: embedded # embedded | external (AUTH_MODE); resolution + insecureAllowHttp: false # Allow http CONTROLLER_PUBLIC_URL in prod (AUTH_INSECURE_ALLOW_HTTP) + bootstrap: # adminUsername: "" # Bootstrap admin username (OIDC_BOOTSTRAP_ADMIN_USERNAME) # adminPassword: "" # Bootstrap admin password (OIDC_BOOTSTRAP_ADMIN_PASSWORD) -# insecureAllowBootstrapLog: false # Allow bootstrap log in prod (AUTH_INSECURE_ALLOW_BOOTSTRAP_LOG) + insecureAllowBootstrapLog: false # Allow bootstrap log in prod (AUTH_INSECURE_ALLOW_BOOTSTRAP_LOG) # issuerUrl: # OIDC issuer URL (OIDC_ISSUER_URL); external mode only # client: # id: # Controller API client ID (OIDC_CLIENT_ID) # secret: # Controller API client secret (OIDC_CLIENT_SECRET) -# viewerClient: # ECN Viewer SPA client ID (OIDC_VIEWER_CLIENT_ID) +# consoleClient: # EdgeOps Console SPA client ID (OIDC_CONSOLE_CLIENT_ID) # rateLimit: # enabled: true # AUTH_RATE_LIMIT_ENABLED # maxRequestsPerWindow: 60 # AUTH_RATE_LIMIT_MAX_REQUESTS — per-IP auth endpoints diff --git a/src/config/embedded-oidc.js b/src/config/embedded-oidc.js index d20ecdbc..5a419f16 100644 --- a/src/config/embedded-oidc.js +++ b/src/config/embedded-oidc.js @@ -13,9 +13,9 @@ const { getOidcSettings } = require('./oidc') const { createOidcProviderAdapterFactory } = require('../data/adapters/oidc-provider-adapter') const { buildUserAccessClaims } = require('../services/auth-token-service') const { loadOidcProviderTtls } = require('./auth-oidc-ttl') -const { getPublicUrl, getViewerUrl } = require('./auth-urls') +const { getPublicUrl, getConsoleUrl } = require('./auth-urls') -const DEFAULT_VIEWER_CLIENT_ID = 'ecn-viewer' +const DEFAULT_CONSOLE_CLIENT_ID = 'ecn-viewer' let providerInstance = null @@ -24,13 +24,13 @@ function getOauthInteractionPath () { } function buildInteractionRedirectUrl (interactionUid) { - const viewerUrl = getViewerUrl() - if (!viewerUrl) { - throw new Error('CONTROLLER_PUBLIC_URL or VIEWER_URL is required for embedded OAuth BFF interactions') + const consoleUrl = getConsoleUrl() + if (!consoleUrl) { + throw new Error('CONTROLLER_PUBLIC_URL or CONSOLE_URL is required for embedded OAuth BFF interactions') } const interactionPath = getOauthInteractionPath() const normalizedPath = interactionPath.startsWith('/') ? interactionPath : `/${interactionPath}` - return `${viewerUrl}${normalizedPath}?interaction=${encodeURIComponent(interactionUid)}` + return `${consoleUrl}${normalizedPath}?interaction=${encodeURIComponent(interactionUid)}` } function buildInteractionPolicy () { @@ -39,19 +39,19 @@ function buildInteractionPolicy () { return policy } -function isViewerClientEnabled () { - const envValue = process.env.AUTH_VIEWER_CLIENT_ENABLED +function isConsoleClientEnabled () { + const envValue = process.env.AUTH_CONSOLE_CLIENT_ENABLED if (envValue !== undefined && envValue !== null && envValue !== '') { return envValue === 'true' || envValue === '1' } - return config.get('auth.viewerClient.enabled', false) === true + return config.get('auth.consoleClient.enabled', false) === true } -function getViewerClientId () { - return process.env.OIDC_VIEWER_CLIENT_ID || - config.get('auth.viewerClient.id') || - config.get('auth.viewerClient') || - DEFAULT_VIEWER_CLIENT_ID +function getConsoleClientId () { + return process.env.OIDC_CONSOLE_CLIENT_ID || + config.get('auth.consoleClient.id') || + config.get('auth.consoleClient') || + DEFAULT_CONSOLE_CLIENT_ID } function getCookieKeys () { @@ -87,12 +87,12 @@ function getTrustProxySetting () { return trustProxy || false } -async function ensureViewerClientMetadata (db) { - if (!isViewerClientEnabled()) { +async function ensureConsoleClientMetadata (db) { + if (!isConsoleClientEnabled()) { return null } - const clientId = getViewerClientId() + const clientId = getConsoleClientId() let clientRow = await db.AuthOidcClient.findOne({ where: { clientId } }) if (!clientRow) { clientRow = await db.AuthOidcClient.create({ @@ -170,9 +170,9 @@ async function ensureSigningJwks (db) { async function buildProviderConfiguration (db) { const clients = [await ensureConfidentialClientMetadata(db)] - const viewerClient = await ensureViewerClientMetadata(db) - if (viewerClient) { - clients.push(viewerClient) + const consoleClient = await ensureConsoleClientMetadata(db) + if (consoleClient) { + clients.push(consoleClient) } const ttlPolicy = await loadOidcProviderTtls(db) diff --git a/src/config/env-mapping.js b/src/config/env-mapping.js index d9fa2dbf..568c4c35 100644 --- a/src/config/env-mapping.js +++ b/src/config/env-mapping.js @@ -29,9 +29,9 @@ module.exports = { TLS_BASE64_CERT: 'server.tls.base64.cert', TLS_BASE64_INTERMEDIATE_CERT: 'server.tls.base64.intermediateCert', - // Viewer Configuration - VIEWER_PORT: 'viewer.port', - VIEWER_URL: 'viewer.url', + // Console Configuration + CONSOLE_PORT: 'console.port', + CONSOLE_URL: 'console.url', // Logging Configuration LOG_LEVEL: 'log.level', @@ -81,8 +81,8 @@ module.exports = { OIDC_ISSUER_URL: 'auth.issuerUrl', OIDC_CLIENT_ID: 'auth.client.id', OIDC_CLIENT_SECRET: 'auth.client.secret', - OIDC_VIEWER_CLIENT_ID: 'auth.viewerClient', - AUTH_VIEWER_CLIENT_ENABLED: 'auth.viewerClient.enabled', + OIDC_CONSOLE_CLIENT_ID: 'auth.consoleClient', + AUTH_CONSOLE_CLIENT_ENABLED: 'auth.consoleClient.enabled', AUTH_INSECURE_ALLOW_HTTP: 'auth.insecureAllowHttp', OIDC_BOOTSTRAP_ADMIN_USERNAME: 'auth.bootstrap.adminUsername', OIDC_BOOTSTRAP_ADMIN_PASSWORD: 'auth.bootstrap.adminPassword', diff --git a/src/main.js b/src/main.js index bb7ec0cf..1528a2f1 100644 --- a/src/main.js +++ b/src/main.js @@ -35,7 +35,7 @@ const getJSONFromURL = async (uri) => { } const apiPort = +(config.get('server.port', 51121)) -const viewerPort = +(process.env.VIEWER_PORT || config.get('viewer.port', 8008)) +const consolePort = +(process.env.CONSOLE_PORT || config.get('console.port', 8008)) const isDaemonElevated = async () => { // If it is running and you can see it, you have enough permission to move forward @@ -61,12 +61,12 @@ const isDaemonElevated = async () => { } const elevatedCommands = ['start', 'stop', 'controller status'] const requiresElevated = async (command, runningAsRoot) => { - // Does ECN Viewer need port 80 ? - if (process.argv[2] === 'start' && (viewerPort < 1024 || apiPort < 1024)) { + // Does EdgeOps Console need port 80 ? + if (process.argv[2] === 'start' && (consolePort < 1024 || apiPort < 1024)) { if (!runningAsRoot) { let message = 'Due to' - if (viewerPort < 1024) { - message += ` ECN Viewer requiring TCP port ${viewerPort},` + if (consolePort < 1024) { + message += ` EdgeOps Console requiring TCP port ${consolePort},` } if (apiPort < 1024) { message += ` iofog-controller REST API requiring TCP port ${apiPort},` diff --git a/src/routes/user.js b/src/routes/user.js index 4464ea69..a52c9258 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -321,15 +321,15 @@ module.exports = [ return } - const { tokens, viewerUrl } = responseObject.body + const { tokens, consoleUrl } = responseObject.body - if (viewerUrl) { + if (consoleUrl) { const fragment = new URLSearchParams() fragment.set('accessToken', tokens.accessToken) if (tokens.refreshToken) { fragment.set('refreshToken', tokens.refreshToken) } - res.redirect(302, `${viewerUrl}/login#${fragment.toString()}`) + res.redirect(302, `${consoleUrl}/login#${fragment.toString()}`) logger.apiRes('GET /api/v3/user/oauth/callback', { args: { statusCode: 302 } }) return } diff --git a/src/server.js b/src/server.js index 1f6f6ffe..9d82392d 100755 --- a/src/server.js +++ b/src/server.js @@ -22,7 +22,6 @@ initialize().then(() => { const bodyParser = require('body-parser') const cookieParser = require('cookie-parser') const express = require('express') - const ecnViewer = process.env.ECN_VIEWER_PATH ? require(`${process.env.ECN_VIEWER_PATH}/package/index.js`) : require('@datasance/ecn-viewer') const fs = require('fs') const helmet = require('helmet') const cors = require('cors') @@ -47,15 +46,26 @@ initialize().then(() => { getSessionStoreConfig, resolveSessionSecret } = require('./config/auth-session-store.js') - const { getPublicUrl, getViewerUrl } = require('./config/auth-urls.js') + const { getPublicUrl, getConsoleUrl } = require('./config/auth-urls.js') - const viewerApp = express() + function resolveConsolePath () { + if (process.env.EDGEOPS_CONSOLE_PATH) { + return process.env.EDGEOPS_CONSOLE_PATH + } + const legacyBuildPath = path.join(__dirname, '..', 'node_modules', '@datasance', 'ecn-viewer', 'build') + if (fs.existsSync(legacyBuildPath)) { + return legacyBuildPath + } + throw new Error('EDGEOPS_CONSOLE_PATH is required (path to EdgeOps Console static build/)') + } + + const consoleApp = express() const app = express() const trustProxy = process.env.TRUST_PROXY || config.get('server.trustProxy', false) if (trustProxy) { app.set('trust proxy', trustProxy === true ? 1 : trustProxy) - viewerApp.set('trust proxy', trustProxy === true ? 1 : trustProxy) + consoleApp.set('trust proxy', trustProxy === true ? 1 : trustProxy) } function validateProductionPublicUrl () { @@ -88,14 +98,14 @@ initialize().then(() => { const devMode = process.env.DEV_MODE || config.get('server.devMode', true) const insecureAllowHttp = config.get('auth.insecureAllowHttp', false) - const viewerURLForCors = getViewerUrl() + const consoleURLForCors = getConsoleUrl() app.use(cors({ origin (origin, callback) { - if (!origin || !viewerURLForCors) { + if (!origin || !consoleURLForCors) { callback(null, true) return } - callback(null, origin === viewerURLForCors) + callback(null, origin === consoleURLForCors) }, credentials: true })) @@ -180,13 +190,13 @@ initialize().then(() => { jobs.push((require(path.join(__dirname, 'jobs', file)) || [])) } - function registerServers (api, viewer) { + function registerServers (api, consoleServer) { process.once('SIGTERM', async function (code) { console.log('SIGTERM received. Shutting down.') await new Promise((resolve) => { api.close(resolve) }) console.log('API Server closed.') - await new Promise((resolve) => { viewer.close(resolve) }) - console.log('Viewer Server closed.') + await new Promise((resolve) => { consoleServer.close(resolve) }) + console.log('Console Server closed.') process.exit(0) }) } @@ -194,11 +204,11 @@ initialize().then(() => { function startHttpServer (apps, ports, jobs) { logger.info('TLS not configured, starting HTTP server.') - const viewerServer = apps.viewer.listen(ports.viewer, function onStart (err) { + const consoleServer = apps.console.listen(ports.console, function onStart (err) { if (err) { logger.error(err) } - logger.info(`==> 🌎 Viewer listening on port ${ports.viewer}. Open up http://localhost:${ports.viewer}/ in your browser.`) + logger.info(`==> 🌎 EdgeOps Console listening on port ${ports.console}. Open up http://localhost:${ports.console}/ in your browser.`) }) const apiServer = apps.api.listen(ports.api, function onStart (err) { if (err) { @@ -212,7 +222,7 @@ initialize().then(() => { const wsServer = WebSocketServer.getInstance() wsServer.initialize(apiServer) logger.info(`==> 🌎 Webscoker API server listening on port ${ports.api}. Open up ws://localhost:${ports.api}/.`) - registerServers(apiServer, viewerServer) + registerServers(apiServer, consoleServer) } const { createSSLOptions } = require('./utils/ssl-utils') @@ -226,11 +236,11 @@ initialize().then(() => { isBase64 }) - const viewerServer = https.createServer(sslOptions, apps.viewer).listen(ports.viewer, function onStart (err) { + const consoleServer = https.createServer(sslOptions, apps.console).listen(ports.console, function onStart (err) { if (err) { logger.error(err) } - logger.info(`==> 🌎 HTTPS Viewer server listening on port ${ports.viewer}. Open up https://localhost:${ports.viewer}/ in your browser.`) + logger.info(`==> 🌎 HTTPS EdgeOps Console server listening on port ${ports.console}. Open up https://localhost:${ports.console}/ in your browser.`) jobs.forEach((job) => job.run()) }) @@ -247,17 +257,18 @@ initialize().then(() => { wsServer.initialize(apiServer) logger.info(`==> 🌎 WSS API server listening on port ${ports.api}. Open up wss://localhost:${ports.api}/.`) - registerServers(apiServer, viewerServer) + registerServers(apiServer, consoleServer) } catch (e) { logger.error('Error loading TLS certificates. Please check your configuration.') } } const apiPort = process.env.API_PORT || config.get('server.port') - const viewerPort = process.env.VIEWER_PORT || config.get('viewer.port') + const consolePort = process.env.CONSOLE_PORT || config.get('console.port') const controlPlane = process.env.CONTROL_PLANE || config.get('app.ControlPlane') const publicUrl = getPublicUrl() - const viewerURL = getViewerUrl() + const consoleURL = getConsoleUrl() + const consolePath = resolveConsolePath() // File-based TLS configuration const tlsKey = process.env.TLS_PATH_KEY || config.get('server.tls.path.key') @@ -272,7 +283,17 @@ initialize().then(() => { const hasFileBasedTLS = !devMode && tlsKey && tlsCert const hasBase64TLS = !devMode && tlsKeyBase64 && tlsCertBase64 - viewerApp.use('/', ecnViewer.middleware(express)) + consoleApp.use(express.static(consolePath, { index: 'index.html' })) + consoleApp.get('*', (req, res, next) => { + if (path.extname(req.path)) { + return next() + } + res.sendFile(path.join(consolePath, 'index.html'), (error) => { + if (error) { + next(error) + } + }) + }) const isDaemon = process.argv[process.argv.length - 1] === 'daemonize2' @@ -312,9 +333,9 @@ initialize().then(() => { }) .forEach(setupJobs) - // Set up controller-config.js for ECN Viewer - const ecnViewerControllerConfigFilePath = path.join(__dirname, '..', 'node_modules', '@datasance', 'ecn-viewer', 'build', 'controller-config.js') - const ecnViewerControllerConfig = { + // Set up controller-config.js for EdgeOps Console + const consoleConfigFilePath = path.join(consolePath, 'controller-config.js') + const consoleConfig = { apiPort, auth: { mode: getAuthMode(), @@ -328,18 +349,16 @@ initialize().then(() => { } } if (publicUrl) { - ecnViewerControllerConfig.publicUrl = publicUrl - } - if (viewerURL) { - ecnViewerControllerConfig.viewerUrl = viewerURL + consoleConfig.publicUrl = publicUrl } + consoleConfig.consoleUrl = consoleURL || publicUrl || `http://localhost:${consolePort}` if (controlPlane) { - ecnViewerControllerConfig.controlPlane = controlPlane + consoleConfig.controlPlane = controlPlane } - const ecnViewerConfigScript = ` - window.controllerConfig = ${JSON.stringify(ecnViewerControllerConfig)} + const consoleConfigScript = ` + window.controllerConfig = ${JSON.stringify(consoleConfig)} ` - fs.writeFileSync(ecnViewerControllerConfigFilePath, ecnViewerConfigScript) + fs.writeFileSync(consoleConfigFilePath, consoleConfigScript) } resolveSessionSecret() @@ -366,8 +385,8 @@ initialize().then(() => { .then(() => { if (hasFileBasedTLS) { startHttpsServer( - { api: app, viewer: viewerApp }, - { api: apiPort, viewer: viewerPort }, + { api: app, console: consoleApp }, + { api: apiPort, console: consolePort }, tlsKey, tlsCert, intermedKey, @@ -376,8 +395,8 @@ initialize().then(() => { ) } else if (hasBase64TLS) { startHttpsServer( - { api: app, viewer: viewerApp }, - { api: apiPort, viewer: viewerPort }, + { api: app, console: consoleApp }, + { api: apiPort, console: consolePort }, tlsKeyBase64, tlsCertBase64, intermedKeyBase64, @@ -386,8 +405,8 @@ initialize().then(() => { ) } else { startHttpServer( - { api: app, viewer: viewerApp }, - { api: apiPort, viewer: viewerPort }, + { api: app, console: consoleApp }, + { api: apiPort, console: consolePort }, jobs ) } diff --git a/src/services/auth-oauth-service.js b/src/services/auth-oauth-service.js index 91d6a89f..85570347 100644 --- a/src/services/auth-oauth-service.js +++ b/src/services/auth-oauth-service.js @@ -16,7 +16,7 @@ const { getOauthClientConfiguration, getAuthMode } = require('../config/oidc') -const { getPublicUrl, getViewerUrl } = require('../config/auth-urls') +const { getPublicUrl, getConsoleUrl } = require('../config/auth-urls') const { getSessionStoreTtlMs } = require('../config/auth-session-store') const AuthTokenService = require('./auth-token-service') @@ -30,8 +30,8 @@ function ensureAuthConfigured () { } function ensureOauthBffReady () { - if (!getViewerUrl()) { - throw new Errors.NotImplementedError('OAuth BFF requires CONTROLLER_PUBLIC_URL or VIEWER_URL to be configured') + if (!getConsoleUrl()) { + throw new Errors.NotImplementedError('OAuth BFF requires CONTROLLER_PUBLIC_URL or CONSOLE_URL to be configured') } } @@ -170,7 +170,7 @@ async function callback (req) { throw new Errors.AuthenticationError(error.message || 'OAuth authorization failed') } - const viewerUrl = getViewerUrl() + const consoleUrl = getConsoleUrl() if (getAuthMode() === 'embedded') { const user = await resolveEmbeddedUserFromTokenResponse(tokenResponse) @@ -178,7 +178,7 @@ async function callback (req) { const tokens = await AuthTokenService.issueTokenPair(user, groupNames) return { tokens, - viewerUrl + consoleUrl } } @@ -189,7 +189,7 @@ async function callback (req) { accessToken: tokenResponse.access_token, refreshToken: tokenResponse.refresh_token || null }, - viewerUrl + consoleUrl } } @@ -197,5 +197,6 @@ module.exports = { authorize, callback, getRedirectUri, - getViewerUrl + getConsoleUrl, + resolveEmbeddedUserFromTokenResponse } diff --git a/src/services/controller-service.js b/src/services/controller-service.js index bd2513d5..7bbdeec0 100644 --- a/src/services/controller-service.js +++ b/src/services/controller-service.js @@ -11,11 +11,39 @@ * */ +const fs = require('fs') +const path = require('path') + const architectureManager = require('../data/managers/architecture-manager') const TransactionDecorator = require('../decorators/transaction-decorator') const packageJson = require('../../package') const AppHelper = require('../helpers/app-helper') +function readVersionFile (filePath) { + try { + if (fs.existsSync(filePath)) { + return fs.readFileSync(filePath, 'utf8').trim() + } + } catch (_) { + // ignore unreadable VERSION file + } + return undefined +} + +function resolveConsoleVersion () { + if (process.env.EDGEOPS_CONSOLE_VERSION) { + return process.env.EDGEOPS_CONSOLE_VERSION + } + const consolePath = process.env.EDGEOPS_CONSOLE_PATH + if (consolePath) { + const fromConsolePath = readVersionFile(path.join(consolePath, 'VERSION')) + if (fromConsolePath) { + return fromConsolePath + } + } + return readVersionFile(path.join(__dirname, '..', '..', 'static', 'console', 'VERSION')) +} + const getArchitectures = async function (isCLI, transaction) { const architectures = await architectureManager.findAll({}, transaction) const response = [] @@ -49,7 +77,7 @@ const statusController = async function (isCLI) { uptimeSec: process.uptime(), versions: { controller: packageJson.version, - ecnViewer: packageJson.dependencies['@datasance/ecn-viewer'] + ecnViewer: resolveConsoleVersion() } } } From 3f8f997b9805303839cba6c685d5460221e30cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 02:17:46 +0300 Subject: [PATCH 59/75] Align unit tests with v3.8 service and controller contracts. Update stubs and expectations for removed userId parameters, renamed agent and config fields, OAuth BFF auth flows, and NATS orchestration behavior. --- test/oidc/README.md | 10 +- test/oidc/run-embedded-smoke.js | 4 +- test/src/config/auth-urls.test.js | 28 +- test/src/config/embedded-oidc.test.js | 6 +- .../application-controller.test.js | 20 +- .../controllers/catalog-controller.test.js | 20 +- .../microservices-controller.test.js | 44 +- test/src/controllers/nats-controller.test.js | 2 +- .../controllers/registry-controller.test.js | 16 +- .../src/controllers/tunnel-controller.test.js | 10 +- test/src/controllers/user-controller.test.js | 463 +--- test/src/helpers/app-helpers.test.js | 2 +- test/src/services/agent-service.test.js | 4 +- test/src/services/application-service.test.js | 684 ++--- test/src/services/auth-oauth-service.test.js | 8 +- test/src/services/catalog-service.test.js | 1060 ++------ test/src/services/iofog-service.test.js | 2107 +++------------ .../services/microservices-service.test.js | 2382 ++--------------- test/src/services/nats-auth-service.test.js | 17 +- test/src/services/nats-service.test.js | 13 +- test/src/services/registry-service.test.js | 592 ++-- test/src/services/router-service.test.js | 135 +- test/src/services/tunnel-service.test.js | 11 +- test/src/template/template-test.js | 2 +- test/support/oidc-test-helpers.js | 4 +- 25 files changed, 1458 insertions(+), 6186 deletions(-) diff --git a/test/oidc/README.md b/test/oidc/README.md index 5d14eee3..ac6595b5 100644 --- a/test/oidc/README.md +++ b/test/oidc/README.md @@ -88,7 +88,7 @@ Point env at any OIDC issuer: - `OIDC_ISSUER_URL` — full issuer URL - `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` — confidential client - `CONTROLLER_PUBLIC_URL` — canonical external URL (issuer host + OAuth callback base) -- `VIEWER_URL` — SPA base; BFF redirects tokens to `{viewerUrl}/login#accessToken=...` +- `CONSOLE_URL` — SPA base; BFF redirects tokens to `{consoleUrl}/login#accessToken=...` Optional auth rate limits (Plan 8.2-4): `AUTH_RATE_LIMIT_ENABLED` (default `true`), `AUTH_RATE_LIMIT_MAX_REQUESTS` (default `60`), `AUTH_RATE_LIMIT_WINDOW_MS` (default `60000`). @@ -116,9 +116,9 @@ middleware (default). 3. **Callback + Viewer handoff** — after IdP redirects to `{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback?code=...&state=...`, expect **302** to - `{VIEWER_URL}/login#accessToken=...&refreshToken=...` when `VIEWER_URL` is set. + `{CONSOLE_URL}/login#accessToken=...&refreshToken=...` when `CONSOLE_URL` is set. -4. **Protected API** — copy `accessToken` from the fragment (or JSON **200** when `VIEWER_URL` +4. **Protected API** — copy `accessToken` from the fragment (or JSON **200** when `CONSOLE_URL` is unset) and call: ```bash @@ -144,7 +144,7 @@ Embedded interaction step state (`AuthInteractionStates`) uses the same store mo ### Two-instance manual procedure -Prerequisites: shared mysql/postgres DB; both instances use identical auth env (`AUTH_MODE`, `CONTROLLER_PUBLIC_URL`, `VIEWER_URL`, `AUTH_SESSION_*`). +Prerequisites: shared mysql/postgres DB; both instances use identical auth env (`AUTH_MODE`, `CONTROLLER_PUBLIC_URL`, `CONSOLE_URL`, `AUTH_SESSION_*`). 1. Start instance A on port `51121` and instance B on port `51122` (different `SERVER_PORT`). @@ -166,6 +166,6 @@ Prerequisites: shared mysql/postgres DB; both instances use identical auth env ( "http://localhost:51122/api/v3/user/oauth/callback?code=&state=" ``` - Expect **302** to `{VIEWER_URL}/login#accessToken=...` (or JSON **200** when `VIEWER_URL` is unset). + Expect **302** to `{CONSOLE_URL}/login#accessToken=...` (or JSON **200** when `CONSOLE_URL` is unset). 5. **Negative control** — with `AUTH_SESSION_STORE_TYPE=memory`, step 4 on a different instance returns **401** (session not found). diff --git a/test/oidc/run-embedded-smoke.js b/test/oidc/run-embedded-smoke.js index 58280a50..fff3f6fa 100644 --- a/test/oidc/run-embedded-smoke.js +++ b/test/oidc/run-embedded-smoke.js @@ -35,8 +35,8 @@ function main () { console.log('Protected route smoke (replace ):') console.log(' curl -H "Authorization: Bearer " http://localhost:51121/api/v3/user/profile') console.log('') - console.log('Embedded OAuth BFF smoke (requires VIEWER_URL + running Controller):') - console.log(" export VIEWER_URL='http://localhost:8008'") + console.log('Embedded OAuth BFF smoke (requires CONSOLE_URL + running Controller):') + console.log(" export CONSOLE_URL='http://localhost:8008'") console.log(' open http://localhost:51121/api/v3/user/oauth/authorize') console.log(' # → redirects to Viewer /login/oauth?interaction=') console.log(' # → Viewer calls POST /api/v3/user/interaction/:uid/login (etc.)') diff --git a/test/src/config/auth-urls.test.js b/test/src/config/auth-urls.test.js index d0909d49..3db4ecf6 100644 --- a/test/src/config/auth-urls.test.js +++ b/test/src/config/auth-urls.test.js @@ -1,10 +1,11 @@ 'use strict' +const sinon = require('sinon') const { expect } = require('chai') +const config = require('../../../src/config') const { snapshotOidcEnv, - restoreOidcEnv, - applyOidcEnv + restoreOidcEnv } = require('../../support/oidc-test-helpers') describe('auth-urls', () => { @@ -12,25 +13,30 @@ describe('auth-urls', () => { afterEach(() => { restoreOidcEnv($envSnapshot) - delete process.env.VIEWER_URL + delete process.env.CONSOLE_URL delete process.env.CONTROLLER_PUBLIC_URL delete require.cache[require.resolve('../../../src/config/auth-urls')] + sinon.restore() }) - it('uses CONTROLLER_PUBLIC_URL when VIEWER_URL is unset', () => { + it('uses CONTROLLER_PUBLIC_URL when CONSOLE_URL is unset', () => { process.env.CONTROLLER_PUBLIC_URL = 'https://controller.example.com/' - delete process.env.VIEWER_URL + delete process.env.CONSOLE_URL + sinon.stub(config, 'get').callsFake((key) => { + if (key === 'console.url') return '' + return '' + }) - const { getPublicUrl, getViewerUrl } = require('../../../src/config/auth-urls') + const { getPublicUrl, getConsoleUrl } = require('../../../src/config/auth-urls') expect(getPublicUrl()).to.equal('https://controller.example.com') - expect(getViewerUrl()).to.equal('https://controller.example.com') + expect(getConsoleUrl()).to.equal('https://controller.example.com') }) - it('prefers explicit VIEWER_URL over CONTROLLER_PUBLIC_URL', () => { + it('prefers explicit CONSOLE_URL over CONTROLLER_PUBLIC_URL', () => { process.env.CONTROLLER_PUBLIC_URL = 'https://controller.example.com' - process.env.VIEWER_URL = 'https://viewer.example.com/' + process.env.CONSOLE_URL = 'https://console.example.com/' - const { getViewerUrl } = require('../../../src/config/auth-urls') - expect(getViewerUrl()).to.equal('https://viewer.example.com') + const { getConsoleUrl } = require('../../../src/config/auth-urls') + expect(getConsoleUrl()).to.equal('https://console.example.com') }) }) diff --git a/test/src/config/embedded-oidc.test.js b/test/src/config/embedded-oidc.test.js index c6603d75..a5ec1f55 100644 --- a/test/src/config/embedded-oidc.test.js +++ b/test/src/config/embedded-oidc.test.js @@ -134,12 +134,12 @@ describe('Embedded OIDC issuer', () => { expect(db.AuthOidcKey.create.firstCall.args[0].keyMaterialEncrypted).to.be.a('string') }) - it('registers the optional ecn-viewer public client when enabled', async () => { + it('registers the optional EdgeOps Console public client when enabled', async () => { applyOidcEnv({ AUTH_MODE: 'embedded', CONTROLLER_PUBLIC_URL: $publicUrl, - AUTH_VIEWER_CLIENT_ENABLED: 'true', - OIDC_VIEWER_CLIENT_ID: 'ecn-viewer' + AUTH_CONSOLE_CLIENT_ENABLED: 'true', + OIDC_CONSOLE_CLIENT_ID: 'ecn-viewer' }) const embeddedOidc = reloadEmbeddedOidcModule() diff --git a/test/src/controllers/application-controller.test.js b/test/src/controllers/application-controller.test.js index 3acd73f5..0b35c112 100644 --- a/test/src/controllers/application-controller.test.js +++ b/test/src/controllers/application-controller.test.js @@ -26,7 +26,7 @@ describe('Application Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.createApplicationEndPoint($req, $user)) + def('subject', () => $subject.createApplicationEndPoint($req)) beforeEach(() => { $sandbox.stub(ApplicationService, 'createApplicationEndPoint').returns($response) @@ -38,7 +38,7 @@ describe('Application Controller', () => { name: $name, description: $description, isActivated: $isActivated, - }, $user, false) + }, false) }) context('when ApplicationService#createApplicationEndPoint fails', () => { @@ -65,7 +65,7 @@ describe('Application Controller', () => { body: {}, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getApplicationsByUserEndPoint($req, $user)) + def('subject', () => $subject.getApplicationsByUserEndPoint($req)) beforeEach(() => { $sandbox.stub(ApplicationService, 'getUserApplicationsEndPoint').returns($response) @@ -73,7 +73,7 @@ describe('Application Controller', () => { it('calls ApplicationService.getUserApplicationsEndPoint with correct args', async () => { await $subject - expect(ApplicationService.getUserApplicationsEndPoint).to.have.been.calledWith($user, false) + expect(ApplicationService.getUserApplicationsEndPoint).to.have.been.calledWith(false) }) context('when ApplicationService#getUserApplicationsEndPoint fails', () => { @@ -104,7 +104,7 @@ describe('Application Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getApplicationEndPoint($req, $user)) + def('subject', () => $subject.getApplicationEndPoint($req)) beforeEach(() => { $sandbox.stub(ApplicationService, 'getApplicationEndPoint').returns($response) @@ -112,7 +112,7 @@ describe('Application Controller', () => { it('calls ApplicationService.getApplicationEndPoint with correct args', async () => { await $subject - expect(ApplicationService.getApplicationEndPoint).to.have.been.calledWith({name: $name}, $user, false) + expect(ApplicationService.getApplicationEndPoint).to.have.been.calledWith({ name: $name }, false) }) context('when ApplicationService#getApplicationEndPoint fails', () => { @@ -152,7 +152,7 @@ describe('Application Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.updateApplicationEndPoint($req, $user)) + def('subject', () => $subject.updateApplicationEndPoint($req)) beforeEach(() => { $sandbox.stub(ApplicationService, 'updateApplicationEndPoint').returns($response) @@ -164,7 +164,7 @@ describe('Application Controller', () => { name: $name, description: $description, isActivated: $isActivated, - }, $oldName, $user, false) + }, $oldName, false) }) context('when ApplicationService#updateApplicationEndPoint fails', () => { @@ -195,7 +195,7 @@ describe('Application Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteApplicationEndPoint($req, $user)) + def('subject', () => $subject.deleteApplicationEndPoint($req)) beforeEach(() => { $sandbox.stub(ApplicationService, 'deleteApplicationEndPoint').returns($response) @@ -203,7 +203,7 @@ describe('Application Controller', () => { it('calls ApplicationService.deleteApplicationEndPoint with correct args', async () => { await $subject - expect(ApplicationService.deleteApplicationEndPoint).to.have.been.calledWith({ name: $name }, $user, false) + expect(ApplicationService.deleteApplicationEndPoint).to.have.been.calledWith({ name: $name }, false) }) context('when ApplicationService.deleteApplicationEndPoint fails', () => { diff --git a/test/src/controllers/catalog-controller.test.js b/test/src/controllers/catalog-controller.test.js index c5aace5c..a3f62e8d 100644 --- a/test/src/controllers/catalog-controller.test.js +++ b/test/src/controllers/catalog-controller.test.js @@ -61,7 +61,7 @@ describe('Catalog Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.createCatalogItemEndPoint($req, $user)) + def('subject', () => $subject.createCatalogItemEndPoint($req)) beforeEach(() => { $sandbox.stub(CatalogService, 'createCatalogItemEndPoint').returns($response) @@ -83,7 +83,7 @@ describe('Catalog Controller', () => { inputType: $inputType, outputType: $outputType, configExample: $configExample, - }, $user) + }) }) context('when CatalogService#createCatalogItemEndPoint fails', () => { @@ -110,7 +110,7 @@ describe('Catalog Controller', () => { body: {}, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.listCatalogItemsEndPoint($req, $user)) + def('subject', () => $subject.listCatalogItemsEndPoint($req)) beforeEach(() => { $sandbox.stub(CatalogService, 'listCatalogItemsEndPoint').returns($response) @@ -118,7 +118,7 @@ describe('Catalog Controller', () => { it('calls CatalogService.listCatalogItemsEndPoint with correct args', async () => { await $subject - expect(CatalogService.listCatalogItemsEndPoint).to.have.been.calledWith($user, false) + expect(CatalogService.listCatalogItemsEndPoint).to.have.been.calledWith(false) }) context('when CatalogService#listCatalogItemsEndPoint fails', () => { @@ -150,7 +150,7 @@ describe('Catalog Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.listCatalogItemEndPoint($req, $user)) + def('subject', () => $subject.listCatalogItemEndPoint($req)) beforeEach(() => { $sandbox.stub(CatalogService, 'getCatalogItemEndPoint').returns($response) @@ -158,7 +158,7 @@ describe('Catalog Controller', () => { it('calls CatalogService.getCatalogItemEndPoint with correct args', async () => { await $subject - expect(CatalogService.getCatalogItemEndPoint).to.have.been.calledWith($id, $user, false) + expect(CatalogService.getCatalogItemEndPoint).to.have.been.calledWith($id, false) }) context('when CatalogService#getCatalogItemEndPoint fails', () => { @@ -189,7 +189,7 @@ describe('Catalog Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteCatalogItemEndPoint($req, $user)) + def('subject', () => $subject.deleteCatalogItemEndPoint($req)) beforeEach(() => { $sandbox.stub(CatalogService, 'deleteCatalogItemEndPoint').returns($response) @@ -197,7 +197,7 @@ describe('Catalog Controller', () => { it('calls CatalogService.deleteCatalogItemEndPoint with correct args', async () => { await $subject - expect(CatalogService.deleteCatalogItemEndPoint).to.have.been.calledWith($id, $user, false) + expect(CatalogService.deleteCatalogItemEndPoint).to.have.been.calledWith($id, false) }) context('when CatalogService#deleteCatalogItemEndPoint fails', () => { @@ -272,7 +272,7 @@ describe('Catalog Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.updateCatalogItemEndPoint($req, $user)) + def('subject', () => $subject.updateCatalogItemEndPoint($req)) beforeEach(() => { $sandbox.stub(CatalogService, 'updateCatalogItemEndPoint').returns($response) @@ -294,7 +294,7 @@ describe('Catalog Controller', () => { inputType: $inputType, outputType: $outputType, configExample: $configExample, - }, $user, false) + }, false) }) context('when CatalogService.updateCatalogItemEndPoint fails', () => { diff --git a/test/src/controllers/microservices-controller.test.js b/test/src/controllers/microservices-controller.test.js index e227628b..5294e0a6 100644 --- a/test/src/controllers/microservices-controller.test.js +++ b/test/src/controllers/microservices-controller.test.js @@ -48,7 +48,7 @@ describe('Microservices Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.createMicroserviceOnFogEndPoint($req, $user)) + def('subject', () => $subject.createMicroserviceOnFogEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'createMicroserviceEndPoint').returns($response) @@ -66,7 +66,7 @@ describe('Microservices Controller', () => { logSize: $logSize, volumeMappings: $volumeMappings, ports: $ports, - }, $user, false) + }, false) }) context('when MicroservicesService#createMicroserviceEndPoint fails', () => { @@ -97,7 +97,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getMicroserviceEndPoint($req, $user)) + def('subject', () => $subject.getMicroserviceEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'getMicroserviceEndPoint').returns($response) @@ -105,7 +105,7 @@ describe('Microservices Controller', () => { it('calls MicroservicesService.getMicroserviceEndPoint with correct args', async () => { await $subject - expect(MicroservicesService.getMicroserviceEndPoint).to.have.been.calledWith($uuid, $user, false) + expect(MicroservicesService.getMicroserviceEndPoint).to.have.been.calledWith($uuid, false) }) context('when MicroservicesService#getMicroserviceEndPoint fails', () => { @@ -157,7 +157,7 @@ describe('Microservices Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.updateMicroserviceEndPoint($req, $user)) + def('subject', () => $subject.updateMicroserviceEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'updateMicroserviceEndPoint').returns($response) @@ -173,7 +173,7 @@ describe('Microservices Controller', () => { rootHostAccess: $rootHostAccess, logSize: $logSize, volumeMappings: $volumeMappings, - }, $user, false) + }, false) }) context('when MicroservicesService#updateMicroserviceEndPoint fails', () => { @@ -208,7 +208,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteMicroserviceEndPoint($req, $user)) + def('subject', () => $subject.deleteMicroserviceEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'deleteMicroserviceEndPoint').returns($response) @@ -218,7 +218,7 @@ describe('Microservices Controller', () => { await $subject expect(MicroservicesService.deleteMicroserviceEndPoint).to.have.been.calledWith($uuid, { withCleanup: $withCleanup, - }, $user, false) + }, false) }) context('when MicroservicesService#deleteMicroserviceEndPoint fails', () => { @@ -248,7 +248,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getMicroservicesByApplicationEndPoint($req, $user)) + def('subject', () => $subject.getMicroservicesByApplicationEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'listMicroservicesEndPoint').returns($response) @@ -256,7 +256,7 @@ describe('Microservices Controller', () => { it('calls MicroservicesService.listMicroservicesEndPoint with correct args', async () => { await $subject - expect(MicroservicesService.listMicroservicesEndPoint).to.have.been.calledWith({ applicationName: $application, flowId: undefined }, $user, false) + expect(MicroservicesService.listMicroservicesEndPoint).to.have.been.calledWith({ applicationName: $application }, false) }) context('when MicroservicesService#listMicroservicesEndPoint fails', () => { @@ -294,7 +294,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.createMicroservicePortMappingEndPoint($req, $user)) + def('subject', () => $subject.createMicroservicePortMappingEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'createPortMappingEndPoint').returns($response) @@ -306,7 +306,7 @@ describe('Microservices Controller', () => { internal: $internal, external: $external, publicMode: $publicMode, - }, $user, false) + }, false) }) context('when MicroservicesService#createPortMappingEndPoint fails', () => { @@ -338,7 +338,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteMicroservicePortMappingEndPoint($req, $user)) + def('subject', () => $subject.deleteMicroservicePortMappingEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'deletePortMappingEndPoint').returns($response) @@ -346,7 +346,7 @@ describe('Microservices Controller', () => { it('calls MicroservicesService.deletePortMappingEndPoint with correct args', async () => { await $subject - expect(MicroservicesService.deletePortMappingEndPoint).to.have.been.calledWith($uuid, $internalPort, $user, false) + expect(MicroservicesService.deletePortMappingEndPoint).to.have.been.calledWith($uuid, $internalPort, false) }) context('when MicroservicesService#deletePortMappingEndPoint fails', () => { @@ -376,7 +376,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getMicroservicePortMappingListEndPoint($req, $user)) + def('subject', () => $subject.getMicroservicePortMappingListEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'listMicroservicePortMappingsEndPoint').returns($response) @@ -384,7 +384,7 @@ describe('Microservices Controller', () => { it('calls MicroservicesService.listMicroservicePortMappingsEndPoint with correct args', async () => { await $subject - expect(MicroservicesService.listMicroservicePortMappingsEndPoint).to.have.been.calledWith($uuid, $user, false) + expect(MicroservicesService.listMicroservicePortMappingsEndPoint).to.have.been.calledWith($uuid, false) }) context('when MicroservicesService#listMicroservicePortMappingsEndPoint fails', () => { @@ -423,7 +423,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve({ id: 15 })) - def('subject', () => $subject.createMicroserviceVolumeMappingEndPoint($req, $user)) + def('subject', () => $subject.createMicroserviceVolumeMappingEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'createVolumeMappingEndPoint').returns($response) @@ -435,7 +435,7 @@ describe('Microservices Controller', () => { hostDestination: $hostDestination, containerDestination: $containerDestination, accessMode: $accessMode, - }, $user, false) + }, false) }) context('when MicroservicesService#createVolumeMappingEndPoint fails', () => { @@ -465,7 +465,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.listMicroserviceVolumeMappingsEndPoint($req, $user)) + def('subject', () => $subject.listMicroserviceVolumeMappingsEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'listVolumeMappingsEndPoint').returns($response) @@ -473,7 +473,7 @@ describe('Microservices Controller', () => { it('calls MicroservicesService.listVolumeMappingsEndPoint with correct args', async () => { await $subject - expect(MicroservicesService.listVolumeMappingsEndPoint).to.have.been.calledWith($uuid, $user, false) + expect(MicroservicesService.listVolumeMappingsEndPoint).to.have.been.calledWith($uuid, false) }) context('when MicroservicesService#listVolumeMappingsEndPoint fails', () => { @@ -505,7 +505,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteMicroserviceVolumeMappingEndPoint($req, $user)) + def('subject', () => $subject.deleteMicroserviceVolumeMappingEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'deleteVolumeMappingEndPoint').returns($response) @@ -513,7 +513,7 @@ describe('Microservices Controller', () => { it('calls MicroservicesService.deleteVolumeMappingEndPoint with correct args', async () => { await $subject - expect(MicroservicesService.deleteVolumeMappingEndPoint).to.have.been.calledWith($uuid, $id, $user, false) + expect(MicroservicesService.deleteVolumeMappingEndPoint).to.have.been.calledWith($uuid, $id, false) }) context('when MicroservicesService#deleteVolumeMappingEndPoint fails', () => { diff --git a/test/src/controllers/nats-controller.test.js b/test/src/controllers/nats-controller.test.js index c2ef51e2..84655261 100644 --- a/test/src/controllers/nats-controller.test.js +++ b/test/src/controllers/nats-controller.test.js @@ -162,7 +162,7 @@ describe('NATS Controller', () => { it('should return all users with account/application context', async () => { const response = await $subject.listAllUsersEndPoint() expect(response).to.eql(payload) - expect(NatsApiService.listAllUsers).to.have.been.calledOnce() + expect(NatsApiService.listAllUsers).to.have.been.calledOnce }) }) diff --git a/test/src/controllers/registry-controller.test.js b/test/src/controllers/registry-controller.test.js index 56dc46d9..62acfd8d 100644 --- a/test/src/controllers/registry-controller.test.js +++ b/test/src/controllers/registry-controller.test.js @@ -34,7 +34,7 @@ describe('Registry Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.createRegistryEndPoint($req, $user)) + def('subject', () => $subject.createRegistryEndPoint($req)) beforeEach(() => { $sandbox.stub(RegistryService, 'createRegistry').returns($response) @@ -50,7 +50,7 @@ describe('Registry Controller', () => { email: $email, requiresCert: $requiresCert, certificate: $certificate, - }, $user) + }) }) context('when RegistryService#createRegistry fails', () => { @@ -77,7 +77,7 @@ describe('Registry Controller', () => { body: {}, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getRegistriesEndPoint($req, $user)) + def('subject', () => $subject.getRegistriesEndPoint($req)) beforeEach(() => { $sandbox.stub(RegistryService, 'findRegistries').returns($response) @@ -85,7 +85,7 @@ describe('Registry Controller', () => { it('calls RegistryService.findRegistries with correct args', async () => { await $subject - expect(RegistryService.findRegistries).to.have.been.calledWith($user, false) + expect(RegistryService.findRegistries).to.have.been.calledWith(false) }) context('when RegistryService#findRegistries fails', () => { @@ -115,7 +115,7 @@ describe('Registry Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteRegistryEndPoint($req, $user)) + def('subject', () => $subject.deleteRegistryEndPoint($req)) beforeEach(() => { $sandbox.stub(RegistryService, 'deleteRegistry').returns($response) @@ -125,7 +125,7 @@ describe('Registry Controller', () => { await $subject expect(RegistryService.deleteRegistry).to.have.been.calledWith({ id: parseInt($req.params.id), - }, $user, false) + }, false) }) context('when RegistryService#deleteRegistry fails', () => { @@ -172,7 +172,7 @@ describe('Registry Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.updateRegistryEndPoint($req, $user)) + def('subject', () => $subject.updateRegistryEndPoint($req)) beforeEach(() => { $sandbox.stub(RegistryService, 'updateRegistry').returns($response) @@ -188,7 +188,7 @@ describe('Registry Controller', () => { email: $email, requiresCert: $requiresCert, certificate: $certificate, - }, $id, $user, false) + }, $id, false) }) context('when RegistryService#updateRegistry fails', () => { diff --git a/test/src/controllers/tunnel-controller.test.js b/test/src/controllers/tunnel-controller.test.js index ca204db8..5f8dd62d 100644 --- a/test/src/controllers/tunnel-controller.test.js +++ b/test/src/controllers/tunnel-controller.test.js @@ -23,7 +23,7 @@ describe('Tunnel Controller', () => { })) def('user', () => 'user!') def('response', () => Promise.resolve()) - def('subject', () => $subject.manageTunnelEndPoint($req, $user)) + def('subject', () => $subject.manageTunnelEndPoint($req)) beforeEach(() => { $sandbox.stub(TunnelService, 'openTunnel').returns($response) @@ -33,7 +33,7 @@ describe('Tunnel Controller', () => { context('when action is "open"', async () => { it('calls TunnelService#openTunnel with correct args', async () => { await $subject - expect(TunnelService.openTunnel).to.have.been.calledWith({ iofogUuid: $id }, $user, false) + expect(TunnelService.openTunnel).to.have.been.calledWith({ iofogUuid: $id }, false) }) context('when TunnelService#openTunnel fails', () => { @@ -58,7 +58,7 @@ describe('Tunnel Controller', () => { it('calls TunnelService#closeTunnel with correct args', async () => { await $subject - expect(TunnelService.closeTunnel).to.have.been.calledWith({ iofogUuid: $id }, $user) + expect(TunnelService.closeTunnel).to.have.been.calledWith({ iofogUuid: $id }) }) context('when TunnelService#closeTunnel fails', () => { @@ -99,7 +99,7 @@ describe('Tunnel Controller', () => { })) def('user', () => 'user!') def('response', () => Promise.resolve()) - def('subject', () => $subject.getTunnelEndPoint($req, $user)) + def('subject', () => $subject.getTunnelEndPoint($req)) beforeEach(() => { $sandbox.stub(TunnelService, 'findTunnel').returns($response) @@ -107,7 +107,7 @@ describe('Tunnel Controller', () => { it('calls TunnelService#findTunnel with correct args', async () => { await $subject - expect(TunnelService.findTunnel).to.have.been.calledWith({ iofogUuid: $id }, $user) + expect(TunnelService.findTunnel).to.have.been.calledWith({ iofogUuid: $id }) }) context('when TunnelService#findTunnel fails', () => { diff --git a/test/src/controllers/user-controller.test.js b/test/src/controllers/user-controller.test.js index 508659ca..855e8e3a 100644 --- a/test/src/controllers/user-controller.test.js +++ b/test/src/controllers/user-controller.test.js @@ -3,474 +3,205 @@ const sinon = require('sinon') const UserController = require('../../../src/controllers/user-controller') const UserService = require('../../../src/services/user-service') -const AppHelper = require('../../../src/helpers/app-helper') const Validator = require('../../../src/schemas') describe('User Controller', () => { - def('subject', () => UserController) + def('controller', () => UserController) def('sandbox', () => sinon.createSandbox()) afterEach(() => $sandbox.restore()) - const error = 'Error!' - - describe('.userSignupEndPoint()', () => { - def('firstName', () => 'firstName') - def('lastName', () => 'lastName') - def('email', () => 'test@gmail.com') - def('password', () => 'testPassword') - - def('req', () => ({ - body: { - firstName: $firstName, - lastName: $lastName, - - email: $email, - password: $password, - }, - })) - def('response', () => Promise.resolve()) - def('encryptedPassword', () => 'encryptedPassword') - def('validatorResponse', () => Promise.resolve(true)) - def('encryptTextResponse', () => $encryptedPassword) - def('subject', () => $subject.userSignupEndPoint($req)) - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'encryptText').returns($encryptTextResponse) - $sandbox.stub(UserService, 'signUp').returns($response) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith({ - firstName: $firstName, - lastName: $lastName, - email: $email, - password: $password, - }, Validator.schemas.signUp) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#encryptText() with correct args', async () => { - await $subject - expect(AppHelper.encryptText).to.have.been.calledWith($password, $email) - }) - - context('when AppHelper#encryptText() fails', () => { - it('fails', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#encryptText() succeeds', () => { - it('calls UserService.signUp with correct args', async () => { - await $subject - expect(UserService.signUp).to.have.been.calledWith({ - firstName: $firstName, - lastName: $lastName, - email: $email, - password: $encryptedPassword, - }, false) - }) - - context('when UserService#signUp fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#signUp succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - describe('.userLoginEndPoint()', () => { def('email', () => 'test@gmail.com') def('password', () => 'testPassword') + def('totp', () => '123456') def('req', () => ({ body: { email: $email, password: $password, - }, + totp: $totp + } })) - def('response', () => Promise.resolve()) - def('validatorResponse', () => Promise.resolve(true)) - def('subject', () => $subject.userLoginEndPoint($req)) + + def('subject', () => $controller.userLoginEndPoint($req)) + def('response', () => Promise.resolve({ accessToken: 'token' })) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) + $sandbox.stub(Validator, 'validate').resolves(true) $sandbox.stub(UserService, 'login').returns($response) }) - it('calls Validator#validate() with correct args', async () => { + it('validates credentials and delegates to UserService.login', async () => { await $subject - expect(Validator.validate).to.have.been.calledWith({ + expect(Validator.validate).to.have.been.calledWith($req.body, Validator.schemas.login) + expect(UserService.login).to.have.been.calledWith({ email: $email, password: $password, - }, Validator.schemas.login) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + totp: $totp + }, false) }) - context('when Validator#validate() succeeds', () => { - it('calls UserService.login with correct args', async () => { - await $subject - expect(UserService.login).to.have.been.calledWith({ - email: $email, - password: $password, - }, false) + context('when validation fails', () => { + beforeEach(() => { + Validator.validate.rejects(new Error('invalid login')) }) - context('when UserService#login fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#login succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) + it('rejects without calling login', () => { + return expect($subject).to.be.rejectedWith('invalid login').then(() => { + expect(UserService.login).to.not.have.been.called }) }) }) }) - describe('.resendActivationEndPoint()', () => { - def('email', () => 'test@gmail.com') + describe('.refreshTokenEndPoint()', () => { + def('refreshToken', () => 'refresh-token-value') def('req', () => ({ - query: { - email: $email, - }, + body: { refreshToken: $refreshToken } })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.resendActivationEndPoint($req)) + + def('subject', () => $controller.refreshTokenEndPoint($req)) + def('response', () => Promise.resolve({ accessToken: 'new-token' })) beforeEach(() => { - $sandbox.stub(UserService, 'resendActivation').returns($response) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(UserService, 'refresh').returns($response) }) - it('calls UserService.resendActivation with correct args', async () => { + it('validates and refreshes tokens', async () => { await $subject - expect(UserService.resendActivation).to.have.been.calledWith({ - email: $email, - }, false) - }) - - context('when UserService#resendActivation fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#resendActivation succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + expect(Validator.validate).to.have.been.calledWith($req.body, Validator.schemas.refresh) + expect(UserService.refresh).to.have.been.calledWith({ refreshToken: $refreshToken }, false) }) }) - describe('.activateUserAccountEndPoint()', () => { - def('activationCode', () => 'testActivationCode') - + describe('.getUserProfileEndPoint()', () => { def('req', () => ({ - body: { - activationCode: $activationCode, - }, + headers: { authorization: 'Bearer access-token' } })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.activateUserAccountEndPoint($req)) - beforeEach(() => { - $sandbox.stub(UserService, 'activateUser').returns($response) - }) - - it('calls UserService.activateUser with correct args', async () => { - await $subject - expect(UserService.activateUser).to.have.been.calledWith({ - activationCode: $activationCode, - }, false) - }) - - context('when UserService#activateUser fails', () => { - const error = 'Error!' + def('profile', () => ({ + firstName: 'Test', + lastName: 'User', + email: 'test@gmail.com' + })) - def('response', () => Promise.reject(error)) + def('subject', () => $controller.getUserProfileEndPoint($req)) - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + beforeEach(() => { + $sandbox.stub(UserService, 'profile').resolves($profile) }) - context('when UserService#activateUser succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + it('returns the user profile from UserService.profile', async () => { + const result = await $subject + expect(UserService.profile).to.have.been.calledWith($req, false) + expect(result).to.eql($profile) }) }) describe('.userLogoutEndPoint()', () => { - def('activationCode', () => 'testActivationCode') - def('user', () => 'user!') - def('req', () => ({ - body: {}, + headers: { authorization: 'Bearer access-token' }, + body: {} })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.userLogoutEndPoint($req, $user)) - beforeEach(() => { - $sandbox.stub(UserService, 'logout').returns($response) - }) + def('subject', () => $controller.userLogoutEndPoint($req)) + def('logoutResult', () => ({ status: 'success' })) - it('calls UserService.logout with correct args', async () => { - await $subject - expect(UserService.logout).to.have.been.calledWith($user, false) + beforeEach(() => { + $sandbox.stub(UserService, 'logout').resolves($logoutResult) }) - context('when UserService#logout fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#logout succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + it('delegates logout to UserService', async () => { + const result = await $subject + expect(UserService.logout).to.have.been.calledWith($req, false) + expect(result).to.eql($logoutResult) }) }) - describe('.getUserProfileEndPoint()', () => { - def('user', () => 'user!') - - def('req', () => ({ - body: {}, + describe('.changePasswordEndPoint()', () => { + def('payload', () => ({ + oldPassword: 'old-password', + newPassword: 'new-password' })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.getUserProfileEndPoint($req, $user)) - - it(`succeeds`, () => { - return expect($subject).to.eventually.include.all.keys(['firstName', 'lastName', 'email']) - }) - }) - - describe('.updateUserProfileEndPoint()', () => { - def('firstName', () => 'firstName2') - def('lastName', () => 'lastName2') - def('user', () => 'user!') def('req', () => ({ - body: { - firstName: $firstName, - lastName: $lastName, - }, + body: $payload, + kauth: { grant: { access_token: { content: { sub: 'user-id' } } } } })) - def('profileData', () => $req.body) - def('response', () => Promise.resolve()) - def('subject', () => $subject.updateUserProfileEndPoint($req, $user)) + + def('subject', () => $controller.changePasswordEndPoint($req)) beforeEach(() => { - $sandbox.stub(UserService, 'updateUserDetails').returns($response) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(UserService, 'changePassword').resolves({ status: 'success' }) }) - it('calls UserService.updateUserDetails with correct args', async () => { + it('validates payload and delegates password change', async () => { await $subject - expect(UserService.updateUserDetails).to.have.been.calledWith($user, $profileData, false) - }) - - context('when UserService#updateUserDetails fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#updateUserDetails succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + expect(Validator.validate).to.have.been.calledWith($payload, Validator.schemas.changePassword) + expect(UserService.changePassword).to.have.been.calledWith($req, $payload, false) }) }) - describe('.deleteUserProfileEndPoint()', () => { - def('user', () => 'user!') - + describe('.enrollMfaEndPoint()', () => { def('req', () => ({ - body: { - force: true, - }, + kauth: { grant: { access_token: { content: { sub: 'user-id' } } } } })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteUserProfileEndPoint($req, $user)) + + def('subject', () => $controller.enrollMfaEndPoint($req)) beforeEach(() => { - $sandbox.stub(UserService, 'deleteUser').returns($response) + $sandbox.stub(UserService, 'enrollMfa').resolves({ secret: 'otp-secret' }) }) - it('calls UserService.deleteUser with correct args', async () => { + it('delegates MFA enrollment to UserService', async () => { await $subject - expect(UserService.deleteUser).to.have.been.calledWith(true, $user, false) - }) - - context('when UserService#deleteUser fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#deleteUser succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + expect(UserService.enrollMfa).to.have.been.calledWith($req, false) }) }) - describe('.updateUserPasswordEndPoint()', () => { - def('user', () => 'user!') - - def('oldPassword', () => 'oldPassword') - def('newPassword', () => 'newPassword') + describe('.interactionLoginEndPoint()', () => { + def('uid', () => 'interaction-uid') + def('email', () => 'test@gmail.com') + def('password', () => 'testPassword') def('req', () => ({ - body: { - oldPassword: $oldPassword, - newPassword: $newPassword, - }, + params: { uid: $uid }, + body: { email: $email, password: $password } })) - def('response', () => Promise.resolve()) - def('validatorResponse', () => Promise.resolve(true)) - def('subject', () => $subject.updateUserPasswordEndPoint($req, $user)) + + def('subject', () => $controller.interactionLoginEndPoint($req)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(UserService, 'updateUserPassword').returns($response) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(UserService, 'interactionLogin').resolves({ status: 'ok' }) }) - it('calls Validator#validate() with correct args', async () => { + it('validates and submits interaction login', async () => { await $subject - expect(Validator.validate).to.have.been.calledWith({ - oldPassword: $oldPassword, - newPassword: $newPassword, - }, Validator.schemas.updatePassword) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls UserService.updateUserPassword with correct args', async () => { - await $subject - expect(UserService.updateUserPassword).to.have.been.calledWith({ - oldPassword: $oldPassword, - newPassword: $newPassword, - }, $user, false) - }) - - context('when UserService#updateUserPassword fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#updateUserPassword succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) + expect(Validator.validate).to.have.been.calledWith($req.body, Validator.schemas.interactionLogin) + expect(UserService.interactionLogin).to.have.been.calledWith( + $uid, + { email: $email, password: $password }, + false + ) }) }) - describe('.resetUserPasswordEndPoint()', () => { - def('user', () => 'user!') - - def('email', () => 'test@gmail.com') - - def('req', () => ({ - body: { - email: $email, - }, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.resetUserPasswordEndPoint($req)) + describe('.oauthAuthorizeEndPoint()', () => { + def('req', () => ({ query: { redirect_uri: 'https://console.example/login' } })) + def('subject', () => $controller.oauthAuthorizeEndPoint($req)) beforeEach(() => { - $sandbox.stub(UserService, 'resetUserPassword').returns($response) + $sandbox.stub(UserService, 'oauthAuthorize').resolves({ redirect: '/oidc/auth' }) }) - it('calls UserService.resetUserPassword with correct args', async () => { + it('delegates OAuth authorize to UserService', async () => { await $subject - expect(UserService.resetUserPassword).to.have.been.calledWith({ - email: $email, - }, false) - }) - - context('when UserService#resetUserPassword fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#resetUserPassword succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + expect(UserService.oauthAuthorize).to.have.been.calledWith($req, false) }) }) }) diff --git a/test/src/helpers/app-helpers.test.js b/test/src/helpers/app-helpers.test.js index 5d906a54..d1e86ec2 100644 --- a/test/src/helpers/app-helpers.test.js +++ b/test/src/helpers/app-helpers.test.js @@ -135,7 +135,7 @@ describe('App Helpers', () => { beforeEach(() => { $sandbox.stub(Config, 'get') - .withArgs('Tunnel:PortRange') + .withArgs('tunnel.portRange') .returns(`${portRangeFrom}-${portRangeTo}`) $sandbox.stub(portscanner, 'findAPortNotInUse').returns(Promise.resolve(availablePort)) diff --git a/test/src/services/agent-service.test.js b/test/src/services/agent-service.test.js index 042ff7d2..e1955aa0 100644 --- a/test/src/services/agent-service.test.js +++ b/test/src/services/agent-service.test.js @@ -437,7 +437,7 @@ describe('Agent Service', () => { const expectedFogUpdate = { networkInterface: agentConfig.networkInterface, - dockerUrl: agentConfig.containerEngineUrl, + containerEngineUrl: agentConfig.containerEngineUrl, diskLimit: agentConfig.diskLimit, diskDirectory: agentConfig.diskDirectory, memoryLimit: agentConfig.memoryLimit, @@ -455,7 +455,7 @@ describe('Agent Service', () => { gpsDevice: agentConfig.gpsDevice, gpsScanFrequency: agentConfig.gpsScanFrequency, edgeGuardFrequency: agentConfig.edgeGuardFrequency, - dockerPruningFrequency: agentConfig.pruningFrequency, + pruningFrequency: agentConfig.pruningFrequency, availableDiskThreshold: agentConfig.availableDiskThreshold, logLevel: agentConfig.logLevel, timeZone: agentConfig.timeZone diff --git a/test/src/services/application-service.test.js b/test/src/services/application-service.test.js index f1904d1f..6be46142 100644 --- a/test/src/services/application-service.test.js +++ b/test/src/services/application-service.test.js @@ -7,28 +7,41 @@ const AppHelper = require('../../../src/helpers/app-helper') const Validator = require('../../../src/schemas') const ChangeTrackingService = require('../../../src/services/change-tracking-service') const MicroserviceService = require('../../../src/services/microservices-service') -const Sequelize = require('sequelize') -const Op = Sequelize.Op -const ErrorMessages = require('../../../src/helpers/error-messages') +const NatsAuthService = require('../../../src/services/nats-auth-service') +const Errors = require('../../../src/helpers/errors') + +const transaction = {} +const isCLI = true + +function buildApplicationRecord (fields = {}) { + return { + id: 42, + name: 'my-app', + description: 'test app', + isActivated: true, + isSystem: false, + natsAccess: false, + natsRuleId: null, + ...fields + } +} + +function stubCreateApplicationDeps (sandbox, { appId = 25, name = 'test-name' } = {}) { + const created = buildApplicationRecord({ id: appId, name }) + + sandbox.stub(Validator, 'validate').resolves(true) + sandbox.stub(ApplicationManager, 'findOne').resolves(null) + sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + sandbox.stub(ApplicationManager, 'create').resolves(created) +} describe('Application Service', () => { - def('subject', () => ApplicationService) + def('service', () => ApplicationService) def('sandbox', () => sinon.createSandbox()) - const isCLI = false - afterEach(() => $sandbox.restore()) describe('.createApplicationEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const applicationId = null - const applicationData = { name: 'test-name', description: 'testDescription', @@ -36,576 +49,281 @@ describe('Application Service', () => { isSystem: false } - const applicationToCreate = { - name: applicationData.name, - description: applicationData.description, - isActivated: applicationData.isActivated, - isSystem: applicationData.isSystem, - userId: user.id, - } - - const response = { - name: applicationData.name, - id: 25, - } - - def('subject', () => $subject.createApplicationEndPoint(applicationData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findApplicationResponse', () => Promise.resolve()) - def('deleteUndefinedFieldsResponse', () => applicationToCreate) - def('createApplicationResponse', () => Promise.resolve(response)) - + def('subject', () => $service.createApplicationEndPoint(applicationData, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ApplicationManager, 'findOne').returns($findApplicationResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(ApplicationManager, 'create').returns($createApplicationResponse) + stubCreateApplicationDeps($sandbox) }) - it('calls Validator#validate() with correct args', async () => { - await $subject + it('validates input and creates an application', async () => { + const result = await $subject expect(Validator.validate).to.have.been.calledWith(applicationData, Validator.schemas.applicationCreate) + expect(ApplicationManager.create).to.have.been.calledOnce + expect(result).to.eql({ id: 25, name: 'test-name' }) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('rejects top-level natsAccess (use natsConfig)', () => { + const badPayload = { ...applicationData, natsAccess: true } + return expect( + $service.createApplicationEndPoint(badPayload, isCLI, transaction) + ).to.be.rejectedWith('natsAccess must be provided under natsConfig.natsAccess') }) - context('when Validator#validate() succeeds', () => { - it('calls ApplicationManager#findOne() with correct args', async () => { - await $subject - const where = applicationId - ? { name: applicationData.name, id: { [Op.ne]: applicationId, userId: user.id } } - : { name: applicationData.name, userId: user.id } - - expect(ApplicationManager.findOne).to.have.been.calledWith(where, transaction) + context('when name already exists', () => { + beforeEach(() => { + ApplicationManager.findOne.resolves(buildApplicationRecord({ name: applicationData.name })) }) - context('when ApplicationManager#findOne() fails', () => { - def('findApplicationResponse', () => Promise.reject(error)) + it('rejects with DuplicatePropertyError', () => expect($subject).to.be.rejectedWith(Errors.DuplicatePropertyError)) + }) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) + context('when microservices are included', () => { + const microservices = [{ name: 'test-msvc' }, { name: 'test-msvc-2' }] + const data = { ...applicationData, microservices } - context('when ApplicationManager#findOne() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject + def('subject', () => $service.createApplicationEndPoint(data, isCLI, transaction)) - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(applicationToCreate) - }) + beforeEach(() => { + $sandbox.stub(MicroserviceService, 'createMicroserviceEndPoint').resolves({ uuid: 'msvc-uuid', name: 'test-msvc' }) + }) - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => error) + it('creates each microservice with application name set', async () => { + await $subject + for (const msvc of microservices) { + expect(MicroserviceService.createMicroserviceEndPoint).to.have.been.calledWith( + { ...msvc, application: applicationData.name }, + isCLI, + transaction + ) + } + }) - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('id') - }) + context('when microservice creation fails', () => { + beforeEach(() => { + MicroserviceService.createMicroserviceEndPoint.rejects(new Error('create failed')) + $sandbox.stub(ApplicationManager, 'findApplicationMicroservices').resolves([]) + $sandbox.stub(NatsAuthService, 'deleteAccountForApplication').resolves() + $sandbox.stub(ApplicationManager, 'delete').resolves() + ApplicationManager.findOne.onFirstCall().resolves(null) + ApplicationManager.findOne.onSecondCall().resolves(buildApplicationRecord({ id: 25, name: applicationData.name })) }) - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls ApplicationManager#create() with correct args', async () => { - await $subject - - expect(ApplicationManager.create).to.have.been.calledWith(applicationToCreate) - }) - - context('when ApplicationManager#create() fails', () => { - def('createApplicationResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('id') - }) - }) - - context('when ApplicationManager#create() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('id') - }) + it('rolls back by deleting the application', () => { + return expect($subject).to.be.rejectedWith('create failed').then(() => { + expect(ApplicationManager.delete).to.have.been.calledWith({ name: applicationData.name }, transaction) }) }) }) }) - - context('when there are microservices to deploy', () => { - const microservices = [{ - name: 'test-msvc', - },{ - name: 'test-msvc-2', - }] - const data = { - ...applicationData, - microservices - } - - def('subject', () => ApplicationService.createApplicationEndPoint(data, user, isCLI, transaction)) - beforeEach(() => { - $sandbox.stub(MicroserviceService, 'createMicroserviceEndPoint') - }) - - it('Should create the microservices', async () => { - await $subject - for (const msvcData of microservices) { - expect(MicroserviceService.createMicroserviceEndPoint).to.have.been.calledWith({ ...msvcData, application: response.name }, user, isCLI, transaction) - } - }) - }) - }) describe('.deleteApplicationEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const name = 'my-app' + const application = buildApplicationRecord({ name }) + const microservices = [{ uuid: 'msvc-1', iofogUuid: 'fog-uuid' }] - const whereObj = { - name, - userId: user.id, - } - - const applicationWithMicroservices = { - microservices: [ - { - iofogUuid: 15, - }, - ], - } - - def('subject', () => $subject.deleteApplicationEndPoint({ name }, user, isCLI, transaction)) - def('deleteUndefinedFieldsResponse', () => whereObj) - def('findApplicationMicroservicesResponse', () => Promise.resolve(applicationWithMicroservices.microservices)) - def('updateChangeTrackingResponse', () => Promise.resolve()) - def('deleteApplicationResponse', () => Promise.resolve()) + def('subject', () => $service.deleteApplicationEndPoint({ name }, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(ApplicationManager, 'findApplicationMicroservices').returns($findApplicationMicroservicesResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - $sandbox.stub(MicroserviceService, 'deleteMicroserviceWithRoutesAndPortMappings') - $sandbox.stub(ApplicationManager, 'delete').returns($deleteApplicationResponse) + $sandbox.stub(ApplicationManager, 'findOne').resolves(application) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + $sandbox.stub(ApplicationManager, 'findApplicationMicroservices').resolves(microservices) + $sandbox.stub(MicroserviceService, 'deleteMicroserviceWithRoutesAndPortMappings').resolves() + $sandbox.stub(ChangeTrackingService, 'update').resolves() + $sandbox.stub(NatsAuthService, 'deleteAccountForApplication').resolves() + $sandbox.stub(ApplicationManager, 'delete').resolves() }) - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { + it('deletes microservices, NATS account, and the application', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(whereObj) + expect(MicroserviceService.deleteMicroserviceWithRoutesAndPortMappings).to.have.been.calledWith(microservices[0], transaction) + expect(NatsAuthService.deleteAccountForApplication).to.have.been.calledWith(application.id, transaction) + expect(ApplicationManager.delete).to.have.been.calledWith({ name }, transaction) }) - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls ApplicationManager#findApplicationMicroservices() with correct args', async () => { - await $subject - - expect(ApplicationManager.findApplicationMicroservices).to.have.been.calledWith({ - name, - }, transaction) - }) - - context('when ApplicationManager#findApplicationMicroservices() fails', () => { - def('findApplicationMicroservicesResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + context('when application is system', () => { + beforeEach(() => { + ApplicationManager.findOne.resolves({ ...application, isSystem: true }) }) - context('when ApplicationManager#findApplicationMicroservices() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.update).to.have.been.calledWith(applicationWithMicroservices.microservices[0].iofogUuid, - ChangeTrackingService.events.microserviceFull, transaction) - }) - - it('should delete microservices with routes and ports', async () => { - await $subject - - for (const msvc of applicationWithMicroservices.microservices) - expect(MicroserviceService.deleteMicroserviceWithRoutesAndPortMappings).to.have.been.calledWith(msvc, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('calls ApplicationManager#delete() with correct args', async () => { - await $subject - - expect(ApplicationManager.delete).to.have.been.calledWith(whereObj, transaction) - }) - - context('when ApplicationManager#delete() fails', () => { - def('deleteApplicationResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ApplicationManager#delete() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) }) }) - describe('.updateApplicationEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const name = 'my-app' + const oldApplication = buildApplicationRecord({ name }) - const oldApplicationData = { - id: 42, - name, - description: 'testDescription', - isActivated: true, - isSystem: false - } - - const applicationData = { - name: 'new-app-name', - description: 'testDescription', - isActivated: false, - isSystem: true - } - - const applicationWithMicroservices = { - microservices: [ - { - iofogUuid: 15, - }, - ], - } - - def('subject', () => $subject.updateApplicationEndPoint(applicationData, name, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findExcludedApplicationResponse', () => Promise.resolve(oldApplicationData)) - def('findApplicationResponse', () => Promise.resolve()) - def('deleteUndefinedFieldsResponse', () => applicationData) - def('updateApplicationResponse', () => Promise.resolve({...applicationData, id: oldApplicationData.id})) - def('findApplicationMicroservicesResponse', () => Promise.resolve(applicationWithMicroservices.microservices)) - def('updateChangeTrackingResponse', () => Promise.resolve()) + def('subject', () => $service.updateApplicationEndPoint($updateData, name, isCLI, transaction)) + def('updateData', () => ({ description: 'updated description', isActivated: true })) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - const stub = $sandbox.stub(ApplicationManager, 'findOne') - stub.withArgs({name, userId: user.id}, transaction).returns($findExcludedApplicationResponse) - stub.returns($findApplicationResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(ApplicationManager, 'update').returns($updateApplicationResponse) - $sandbox.stub(ApplicationManager, 'findApplicationMicroservices').returns($findApplicationMicroservicesResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ApplicationManager, 'findOne').callsFake((where) => { + if (where && where.name === name && !where.id) { + return Promise.resolve(oldApplication) + } + return Promise.resolve(null) + }) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + $sandbox.stub(ApplicationManager, 'update').resolves() }) - it('calls Validator#validate() with correct args', async () => { + it('updates mutable application fields', async () => { await $subject - expect(Validator.validate).to.have.been.calledWith(applicationData, Validator.schemas.applicationUpdate) + expect(ApplicationManager.update).to.have.been.calledWith({ id: oldApplication.id }, sinon.match.object, transaction) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) + context('when renaming', () => { + def('updateData', () => ({ name: 'new-app-name' })) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('rejects rename attempts', () => expect($subject).to.be.rejectedWith('Application Resource Name is immutable')) }) - context('when Validator#validate() succeeds', () => { - it('calls ApplicationManager#findOneWithAttributes() with correct args', async () => { - await $subject - expect(ApplicationManager.findOne).to.have.been.calledWith({ name, userId: user.id }, transaction) - }) - - context('when ApplicationManager#findOneWithAttributes() fails', () => { - def('findExcludedApplicationResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ApplicationManager#findOneWithAttributes() succeeds', () => { - it('calls ApplicationManager#findOne() with correct args', async () => { - await $subject - - const where = oldApplicationData.id - ? { name: applicationData.name, userId: user.id, id: { [Op.ne]: oldApplicationData.id } } - : { name: applicationData.name, userId: user.id } - expect(ApplicationManager.findOne).to.have.been.calledWith(where, transaction) - }) - - context('when ApplicationManager#findOne() fails', () => { - def('findApplicationResponse', () => Promise.reject(AppHelper.formatMessage(ErrorMessages.DUPLICATE_NAME, - applicationData.name))) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(AppHelper.formatMessage(ErrorMessages.DUPLICATE_NAME, - applicationData.name)) - }) - }) - - context('when ApplicationManager#findOne() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(applicationData) - }) + context('when microservices are included', () => { + const existingMsvc = { name: 'test-msvc', uuid: 'msvc-1', iofogUuid: 'fog-1' } + const removedMsvc = { name: 'old-msvc', uuid: 'old-uuid', iofogUuid: 'fog-2' } + const newMsvc = { name: 'new-msvc' } + const updateData = { + description: 'updated', + microservices: [existingMsvc, newMsvc] + } - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => Promise.reject(error)) + def('subject', () => $service.updateApplicationEndPoint(updateData, name, isCLI, transaction)) - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls ApplicationManager#update() with correct args', async () => { - await $subject - - const where = isCLI - ? { id: oldApplicationData.id } - : { id: oldApplicationData.id, userId: user.id } - expect(ApplicationManager.update).to.have.been.calledWith(where, applicationData, transaction) - }) - - context('when ApplicationManager#update() fails', () => { - def('updateApplicationResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ApplicationManager#update() succeeds', () => { - it('calls ApplicationManager#findApplicationMicroservices() with correct args', async () => { - await $subject - - expect(ApplicationManager.findApplicationMicroservices).to.have.been.calledWith({ - name, - }, transaction) - }) - - context('when ApplicationManager#findApplicationMicroservices() fails', () => { - def('findApplicationMicroservicesResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ApplicationManager#findApplicationMicroservices() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.update).to.have.been.calledWith(applicationWithMicroservices.microservices[0].iofogUuid, - ChangeTrackingService.events.microserviceFull, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) + beforeEach(() => { + $sandbox.stub(ApplicationManager, 'findApplicationMicroservices').resolves([existingMsvc, removedMsvc]) + $sandbox.stub(MicroserviceService, 'updateMicroserviceEndPoint').resolves({ + microserviceIofogUuid: 'fog-1', + updatedMicroserviceIofogUuid: 'fog-1' }) + $sandbox.stub(MicroserviceService, 'createMicroserviceEndPoint').resolves({ uuid: 'new-uuid', name: 'new-msvc' }) + $sandbox.stub(MicroserviceService, 'deleteMicroserviceWithRoutesAndPortMappings').resolves() + $sandbox.stub(MicroserviceService, 'updateChangeTracking').resolves() + $sandbox.stub(ChangeTrackingService, 'update').resolves() }) - }) - - context('when there are microservices to update', () => { - const newMsvc = { name: 'new-msvc' } - const oldMsvc = { name: 'old-msvc', uuid: 'old-msvc-uuid' } - const msvcs = [{ - name: 'test-msvc', - uuid: 'msvc-1' - },{ - name: 'test-msvc-2', - uuid: 'msvc-2' - }] - const data = { - ...applicationData, - microservices: [...msvcs, newMsvc] - } - const microserviceUuids = { - microserviceIofogUuid: 'msvc-1', - updatedMicroserviceIofogUuid: 'msvc-2' - } - def('subject', () => ApplicationService.updateApplicationEndPoint(data, name, user, isCLI, transaction)) - def('findApplicationMicroservicesResponse', () => Promise.resolve([...msvcs, oldMsvc])) - def('updateResponse',() => Promise.resolve(microserviceUuids)) - beforeEach(() => { - $sandbox.stub(MicroserviceService, 'createMicroserviceEndPoint') - $sandbox.stub(MicroserviceService, 'updateMicroserviceEndPoint').returns($updateResponse) - $sandbox.stub(MicroserviceService, 'deleteMicroserviceWithRoutesAndPortMappings') - }) - it('Should update the microservices', async () => { + it('updates, creates, and deletes microservices as needed', async () => { await $subject - for (const msvcData of msvcs) { - expect(MicroserviceService.updateMicroserviceEndPoint).to.have.been.calledWith(msvcData.uuid, {...msvcData, application: applicationData.name}, user, isCLI, transaction) - } - expect(MicroserviceService.createMicroserviceEndPoint).to.have.been.calledWith({ ...newMsvc, application: applicationData.name }, user, isCLI, transaction) - expect(MicroserviceService.deleteMicroserviceWithRoutesAndPortMappings).to.have.been.calledWith(oldMsvc, transaction) + expect(MicroserviceService.updateMicroserviceEndPoint).to.have.been.calledWith( + existingMsvc.uuid, + { ...existingMsvc, application: name }, + isCLI, + transaction, + false + ) + expect(MicroserviceService.createMicroserviceEndPoint).to.have.been.calledWith( + { ...newMsvc, application: name }, + isCLI, + transaction + ) + expect(MicroserviceService.deleteMicroserviceWithRoutesAndPortMappings).to.have.been.calledWith(removedMsvc, transaction) }) }) }) - describe('.getUserApplicationsEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const application = { - userId: user.id, - isSystem: false - } + describe('.patchApplicationEndPoint()', () => { + const conditions = { name: 'my-app' } + const oldApplication = buildApplicationRecord(conditions) + const patchData = { description: 'patched description', isActivated: true } - def('subject', () => $subject.getUserApplicationsEndPoint(user, isCLI, transaction)) - def('findExcludedApplicationResponse', () => Promise.resolve([])) + def('subject', () => $service.patchApplicationEndPoint($patchData, conditions, isCLI, transaction)) + def('patchData', () => patchData) beforeEach(() => { - $sandbox.stub(ApplicationManager, 'findAllPopulated').returns($findExcludedApplicationResponse) - $sandbox.stub(MicroserviceService, 'buildGetMicroserviceResponse').callsFake(async (m) => m) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ApplicationManager, 'findOne').resolves(oldApplication) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + $sandbox.stub(ApplicationManager, 'update').resolves() }) - it('calls ApplicationManager#findAllWithAttributes() with correct args', async () => { + it('patches application metadata', async () => { await $subject - const attributes = { exclude: ['created_at', 'updated_at'] } - expect(ApplicationManager.findAllPopulated).to.have.been.calledWith(application, attributes, transaction) + expect(Validator.validate).to.have.been.calledWith(patchData, Validator.schemas.applicationPatch) + expect(ApplicationManager.update).to.have.been.calledWith({ id: oldApplication.id }, sinon.match.object, transaction) }) - context('when ApplicationManager#findAllWithAttributes() fails', () => { - def('findExcludedApplicationResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when application is missing', () => { + beforeEach(() => { + ApplicationManager.findOne.resolves(null) }) + + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) - context('when ApplicationManager#findAllWithAttributes() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('applications') - }) + context('when renaming', () => { + def('patchData', () => ({ name: 'new-name' })) + + it('rejects rename attempts', () => expect($subject).to.be.rejectedWith('Application Resource Name is immutable')) }) }) + describe('.getUserApplicationsEndPoint()', () => { + const appRow = buildApplicationRecord() - describe('.getAllApplicationsEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - def('subject', () => $subject.getAllApplicationsEndPoint(isCLI, transaction)) - def('findAllApplicationsResponse', () => Promise.resolve([])) + def('subject', () => $service.getUserApplicationsEndPoint(isCLI, transaction)) beforeEach(() => { - $sandbox.stub(ApplicationManager, 'findAllPopulated').returns($findAllApplicationsResponse) + $sandbox.stub(ApplicationManager, 'findAllPopulated').resolves([appRow]) $sandbox.stub(MicroserviceService, 'buildGetMicroserviceResponse').callsFake(async (m) => m) }) - it('calls ApplicationManager#findAllWithAttributes() with correct args', async () => { - await $subject - const attributes = { exclude: ['created_at', 'updated_at'] } - expect(ApplicationManager.findAllPopulated).to.have.been.calledWith({}, attributes, transaction) + it('lists non-system applications', async () => { + const result = await $subject + expect(ApplicationManager.findAllPopulated).to.have.been.calledWith( + { isSystem: false }, + { exclude: ['created_at', 'updated_at'] }, + transaction + ) + expect(result.applications).to.have.length(1) + expect(result.applications[0].natsConfig).to.eql({ natsAccess: false, natsRule: null }) }) + }) - context('when ApplicationManager#findAllWithAttributes() fails', () => { - def('findAllApplicationsResponse', () => Promise.reject(error)) + describe('.getAllApplicationsEndPoint()', () => { + def('subject', () => $service.getAllApplicationsEndPoint(isCLI, transaction)) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + beforeEach(() => { + $sandbox.stub(ApplicationManager, 'findAllPopulated').resolves([]) }) - context('when ApplicationManager#findAllWithAttributes() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('applications') - }) + it('lists all applications', async () => { + await $subject + expect(ApplicationManager.findAllPopulated).to.have.been.calledWith( + {}, + { exclude: ['created_at', 'updated_at'] }, + transaction + ) }) }) describe('.getApplication()', () => { - const transaction = {} - const error = 'Error!' - const name = 'my-app' + const appRow = buildApplicationRecord({ name }) - const user = { - id: 15, - } - - def('subject', () => $subject.getApplication({ name }, user, isCLI, transaction)) - def('findApplicationResponse', () => Promise.resolve({})) + def('subject', () => $service.getApplication({ name }, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(ApplicationManager, 'findOnePopulated').returns($findApplicationResponse) + $sandbox.stub(ApplicationManager, 'findOnePopulated').resolves(appRow) $sandbox.stub(MicroserviceService, 'buildGetMicroserviceResponse').callsFake(async (m) => m) }) - it('calls ApplicationManager#findOneWithAttributes() with correct args', async () => { - await $subject - const where = isCLI - ? { name } - : { name, userId: user.id } - const attributes = { exclude: ['created_at', 'updated_at'] } - expect(ApplicationManager.findOnePopulated).to.have.been.calledWith(where, attributes, transaction) + it('returns application with natsConfig', async () => { + const result = await $subject + expect(ApplicationManager.findOnePopulated).to.have.been.calledWith( + { name, isSystem: false }, + { exclude: ['created_at', 'updated_at'] }, + transaction + ) + expect(result.natsConfig).to.eql({ natsAccess: false, natsRule: null }) }) - context('when ApplicationManager#findOneWithAttributes() fails', () => { - def('findApplicationResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when application is missing', () => { + beforeEach(() => { + ApplicationManager.findOnePopulated.resolves(null) }) - }) - context('when ApplicationManager#findOneWithAttributes() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal({}) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) }) diff --git a/test/src/services/auth-oauth-service.test.js b/test/src/services/auth-oauth-service.test.js index a7029367..f303ad94 100644 --- a/test/src/services/auth-oauth-service.test.js +++ b/test/src/services/auth-oauth-service.test.js @@ -29,13 +29,13 @@ describe('Auth OAuth service', () => { def('sandbox', () => sinon.createSandbox()) def('envSnapshot', () => snapshotOidcEnv()) def('harness', async () => createEmbeddedAuthHarness($sandbox, { - env: { VIEWER_URL: 'http://viewer.test' } + env: { CONSOLE_URL: 'http://console.test' } })) def('oidcConfig', () => createTestOidcConfiguration()) beforeEach(async () => { await $harness - process.env.VIEWER_URL = 'http://viewer.test' + process.env.CONSOLE_URL = 'http://console.test' delete require.cache[require.resolve('../../../src/services/auth-oauth-service')] const oidcModule = require('../../../src/config/oidc') $sandbox.stub(oidcModule, 'getOauthClientConfiguration').resolves($oidcConfig) @@ -64,7 +64,7 @@ describe('Auth OAuth service', () => { it('passes pkceCodeVerifier to authorizationCodeGrant on callback', async () => { applyExternalEnv({}) - process.env.VIEWER_URL = 'http://viewer.test' + process.env.CONSOLE_URL = 'http://console.test' const grantStub = $sandbox.stub().resolves({ access_token: 'external-access-token', @@ -114,7 +114,7 @@ describe('Auth OAuth service', () => { expect(grantStub.firstCall.args[2].expectedNonce).to.equal('test-nonce') expect(result.tokens.accessToken).to.equal('external-access-token') expect(result.tokens.refreshToken).to.equal('external-refresh-token') - expect(result.viewerUrl).to.equal('http://viewer.test') + expect(result.consoleUrl).to.equal('http://console.test') expect(req.session.controllerOauth).to.be.undefined }) diff --git a/test/src/services/catalog-service.test.js b/test/src/services/catalog-service.test.js index 65b218cc..581ad871 100644 --- a/test/src/services/catalog-service.test.js +++ b/test/src/services/catalog-service.test.js @@ -9,994 +9,292 @@ const CatalogItemInputTypeManager = require('../../../src/data/managers/catalog- const CatalogItemOutputTypeManager = require('../../../src/data/managers/catalog-item-output-type-manager') const RegistryManager = require('../../../src/data/managers/registry-manager') const AppHelper = require('../../../src/helpers/app-helper') -const Sequelize = require('sequelize') -const Op = Sequelize.Op const MicroserviceManager = require('../../../src/data/managers/microservice-manager') +const ChangeTrackingService = require('../../../src/services/change-tracking-service') +const DBConstants = require('../../../src/data/constants') +const ErrorMessages = require('../../../src/helpers/error-messages') +const Errors = require('../../../src/helpers/errors') + +const transaction = {} +const isCLI = true + +function buildCatalogItem (fields = {}) { + return { + id: 15, + name: 'test-catalog', + description: 'desc', + category: 'USER', + publisher: 'Acme', + registryId: 1, + isPublic: true, + ...fields + } +} + +function buildCreatePayload (fields = {}) { + return { + name: 'test-catalog', + description: 'desc', + category: 'USER', + publisher: 'Acme', + registryId: 1, + isPublic: true, + images: [{ containerImage: 'demo:latest', archId: 1 }], + inputType: { infoType: 'json', infoFormat: 'object' }, + outputType: { infoType: 'json', infoFormat: 'object' }, + ...fields + } +} + +function stubCreateCatalogDeps (sandbox, { itemId = 15 } = {}) { + const created = buildCatalogItem({ id: itemId }) + + sandbox.stub(Validator, 'validate').resolves(true) + sandbox.stub(CatalogItemManager, 'findOne').resolves(null) + sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + sandbox.stub(RegistryManager, 'findOne').resolves({ id: 1 }) + sandbox.stub(CatalogItemManager, 'create').resolves(created) + sandbox.stub(CatalogItemImageManager, 'bulkCreate').resolves() + sandbox.stub(CatalogItemInputTypeManager, 'create').resolves() + sandbox.stub(CatalogItemOutputTypeManager, 'create').resolves() +} describe('Catalog Service', () => { - def('subject', () => CatalogService) + def('service', () => CatalogService) def('sandbox', () => sinon.createSandbox()) afterEach(() => $sandbox.restore()) describe('.createCatalogItemEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const data = { - 'name': 'testName', - 'description': 'string', - 'category': 'string', - 'images': [ - { - 'containerImage': 'x86 docker image name', - 'archId': 1, - }, - { - 'containerImage': 'ARM docker image name', - 'archId': 2, - }, - ], - 'publisher': 'string', - 'diskRequired': 0, - 'ramRequired': 0, - 'picture': 'string', - 'isPublic': true, - 'registryId': 1, - 'inputType': { - 'infoType': 'string', - 'infoFormat': 'string', - }, - 'outputType': { - 'infoType': 'string', - 'infoFormat': 'string', - }, - 'configExample': 'string', - } - - const catalogItem = { - id: 15, - name: data.name, - description: data.description, - category: data.category, - configExample: data.configExample, - publisher: data.publisher, - diskRequired: data.diskRequired, - ramRequired: data.ramRequired, - picture: data.picture, - isPublic: data.isPublic, - registryId: data.registryId, - userId: user.id, - } - - const catalogItemImages = data.images.map((image) => ({ - archId: image.archId, - catalogItemId: catalogItem.id, - containerImage: image.containerImage - })) - - const catalogItemInputType = { - catalogItemId: catalogItem.id, - } - - if (data.inputType) { - catalogItemInputType.infoType = data.inputType.infoType - catalogItemInputType.infoFormat = data.inputType.infoFormat - } - - const catalogItemOutputType = { - catalogItemId: catalogItem.id, - } - - if (data.outputType) { - catalogItemOutputType.infoType = data.outputType.infoType - catalogItemOutputType.infoFormat = data.outputType.infoFormat - } - - def('subject', () => $subject.createCatalogItemEndPoint(data, user, transaction)) - - def('validatorResponse', () => Promise.resolve(true)) - def('catalogItemFindResponse', () => Promise.resolve()) - def('deleteUndefinedFieldsResponse1', () => catalogItem) - def('deleteUndefinedFieldsResponse2', () => catalogItemInputType) - def('deleteUndefinedFieldsResponse3', () => catalogItemOutputType) - def('registryFindResponse', () => Promise.resolve({})) - def('catalogItemCreateResponse', () => Promise.resolve(catalogItem)) - def('catalogItemImageCreateResponse', () => Promise.resolve()) - def('catalogItemInputTypeCreateResponse', () => Promise.resolve()) - def('catalogItemOutputTypeCreateResponse', () => Promise.resolve({})) + const data = buildCreatePayload() + def('subject', () => $service.createCatalogItemEndPoint(data, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields') - .onFirstCall().returns($deleteUndefinedFieldsResponse1) - .onSecondCall().returns($deleteUndefinedFieldsResponse2) - .onThirdCall().returns($deleteUndefinedFieldsResponse3) - $sandbox.stub(RegistryManager, 'findOne').returns($registryFindResponse) - $sandbox.stub(CatalogItemManager, 'create').returns($catalogItemCreateResponse) - $sandbox.stub(CatalogItemImageManager, 'bulkCreate').returns($catalogItemImageCreateResponse) - $sandbox.stub(CatalogItemInputTypeManager, 'create').returns($catalogItemInputTypeCreateResponse) - $sandbox.stub(CatalogItemOutputTypeManager, 'create').returns($catalogItemOutputTypeCreateResponse) + stubCreateCatalogDeps($sandbox) }) - it('calls Validator#validate() with correct args', async () => { - await $subject + it('validates input and creates catalog item with dependencies', async () => { + const result = await $subject expect(Validator.validate).to.have.been.calledWith(data, Validator.schemas.catalogItemCreate) + expect(CatalogItemManager.create).to.have.been.calledOnce + expect(CatalogItemImageManager.bulkCreate).to.have.been.calledOnce + expect(CatalogItemInputTypeManager.create).to.have.been.calledOnce + expect(CatalogItemOutputTypeManager.create).to.have.been.calledOnce + expect(result).to.eql({ id: 15 }) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('rejects restricted publisher names', () => { + const badPayload = buildCreatePayload({ publisher: 'Eclipse ioFog' }) + return expect($service.createCatalogItemEndPoint(badPayload, transaction)) + .to.be.rejectedWith(ErrorMessages.RESTRICTED_PUBLISHER) }) - context('when Validator#validate() succeeds', () => { - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject - const where = catalogItem.id - ? { [Op.or]: [{ userId: catalogItem.userId }, { userId: null }], name: data.name, id: { [Op.ne]: catalogItem.id } } - : { [Op.or]: [{ userId: catalogItem.userId }, { userId: null }], name: data.name } - expect(CatalogItemManager.findOne).to.have.been.calledWith(where, transaction) - }) - - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemManager#findOne() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(catalogItem) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse1', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('id') - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls CatalogItemManager#create() with correct args', async () => { - await $subject - expect(CatalogItemManager.create).to.have.been.calledWith(catalogItem, transaction) - }) - - context('when CatalogItemManager#create() fails', () => { - def('catalogItemCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemManager#create() succeeds', () => { - it('calls CatalogItemImageManager#bulkCreate() with correct args', async () => { - await $subject - expect(CatalogItemImageManager.bulkCreate).to.have.been.calledWith(catalogItemImages, transaction) - }) - - context('when CatalogItemImageManager#bulkCreate() fails', () => { - def('catalogItemImageCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemImageManager#bulkCreate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(catalogItemInputType) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse2', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('id') - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls RegistryManager#findOne() with correct args', async () => { - await $subject - expect(RegistryManager.findOne).to.have.been.calledWith({ - id: data.registryId, - }, transaction) - }) - - context('when RegistryManager#findOne() fails', () => { - def('registryFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when RegistryManager#findOne() succeeds', () => { - it('calls CatalogItemInputTypeManager#create() with correct args', async () => { - await $subject - expect(CatalogItemInputTypeManager.create).to.have.been.calledWith(catalogItemInputType) - }) - - context('when CatalogItemInputTypeManager#create() fails', () => { - def('catalogItemInputTypeCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemInputTypeManager#create() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(catalogItemOutputType) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse3', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('id') - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls CatalogItemOutputTypeManager#create() with correct args', async () => { - await $subject - expect(CatalogItemOutputTypeManager.create).to.have.been.calledWith(catalogItemOutputType) - }) - - context('when CatalogItemOutputTypeManager#create() fails', () => { - def('catalogItemOutputTypeCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemOutputTypeManager#create() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.have.property('id') - }) - }) - }) - }) - }) - }) - }) - }) - }) + context('when name already exists', () => { + beforeEach(() => { + CatalogItemManager.findOne.resolves(buildCatalogItem()) }) - }) - }) - - describe('.updateCatalogItemEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const id = 25 - - const data = { - 'name': 'string', - 'description': 'string', - 'category': 'string', - 'images': [ - { - 'containerImage': 'x86 docker image name', - 'archId': 1, - }, - { - 'containerImage': 'ARM docker image name', - 'archId': 2, - }, - ], - 'publisher': 'string', - 'diskRequired': 0, - 'ramRequired': 0, - 'picture': 'string', - 'isPublic': true, - 'registryId': 1, - 'inputType': { - 'infoType': 'string', - 'infoFormat': 'string', - }, - 'outputType': { - 'infoType': 'string', - 'infoFormat': 'string', - }, - 'configExample': 'string', - } - - const isCLI = false - const where = isCLI - ? { id: id } - : { id: id, userId: user.id } - - data.id = id - - - const catalogItem = { - name: data.name, - description: data.description, - category: data.category, - configExample: data.configExample, - publisher: data.publisher, - diskRequired: data.diskRequired, - ramRequired: data.ramRequired, - picture: data.picture, - isPublic: data.isPublic, - registryId: data.registryId, - } - - const image1 = { archId: 1 } - const image2 = { archId: 2 } - - const updatedImage1 = { - archId: 1, - containerImage: 'x86 docker image name', - } - - const updatedImage2 = { - archId: 2, - containerImage: 'ARM docker image name', - } - - const catalogItemInputType = { - catalogItemId: id, - } - - if (data.inputType) { - catalogItemInputType.infoType = data.inputType.infoType - catalogItemInputType.infoFormat = data.inputType.infoFormat - } - - const catalogItemOutputType = { - catalogItemId: id, - } - - if (data.outputType) { - catalogItemOutputType.infoType = data.outputType.infoType - catalogItemOutputType.infoFormat = data.outputType.infoFormat - } - - def('subject', () => $subject.updateCatalogItemEndPoint(id, data, user, isCLI, transaction)) - - def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse1', () => catalogItem) - def('deleteUndefinedFieldsResponse2', () => catalogItemInputType) - def('deleteUndefinedFieldsResponse3', () => catalogItemOutputType) - def('isEmptyResponse', () => false) - def('registryFindResponse', () => Promise.resolve({})) - def('catalogItemFindResponse1', () => Promise.resolve(catalogItem)) - def('catalogItemFindResponse2', () => Promise.resolve()) - def('catalogItemUpdateResponse', () => Promise.resolve()) - def('catalogItemImageUpdateOrCreateResponse', () => Promise.resolve()) - def('catalogItemInputTypeUpdateOrCreateResponse', () => Promise.resolve()) - def('catalogItemOutputTypeUpdateOrCreateResponse', () => Promise.resolve({})) - def('microservicesResponse', () => Promise.resolve([])) - - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields') - .onFirstCall().returns($deleteUndefinedFieldsResponse1) - .onSecondCall().returns($deleteUndefinedFieldsResponse2) - .onThirdCall().returns($deleteUndefinedFieldsResponse3) - $sandbox.stub(AppHelper, 'isEmpty').returns($isEmptyResponse) - $sandbox.stub(RegistryManager, 'findOne').returns($registryFindResponse) - $sandbox.stub(CatalogItemManager, 'findOne') - .onCall(0).returns($catalogItemFindResponse1) - .onCall(1).returns($catalogItemFindResponse2) - .onCall(2).returns($catalogItemFindResponse1) - .onCall(3).returns($catalogItemFindResponse2) - $sandbox.stub(CatalogItemManager, 'update').returns($catalogItemUpdateResponse) - $sandbox.stub(CatalogItemImageManager, 'updateOrCreate').returns($catalogItemImageUpdateOrCreateResponse) // twice - $sandbox.stub(CatalogItemInputTypeManager, 'updateOrCreate').returns($catalogItemInputTypeUpdateOrCreateResponse) - $sandbox.stub(CatalogItemOutputTypeManager, 'updateOrCreate').returns($catalogItemOutputTypeUpdateOrCreateResponse) - // TODO test success fail and arguments - $sandbox.stub(MicroserviceManager, 'findAllWithStatuses').returns($microservicesResponse) - }) - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(data, Validator.schemas.catalogItemUpdate) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('rejects with DuplicatePropertyError', () => expect($subject).to.be.rejectedWith(Errors.DuplicatePropertyError)) }) - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(catalogItem) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse1', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + context('when registry is missing', () => { + beforeEach(() => { + RegistryManager.findOne.resolves(null) }) - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls AppHelper#isEmpty() with correct args', async () => { - await $subject - expect(AppHelper.isEmpty).to.have.been.calledWith(catalogItem) - }) - - context('when AppHelper#isEmpty() fails', () => { - def('isEmptyResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#isEmpty() succeeds', () => { - it('calls RegistryManager#findOne() with correct args', async () => { - await $subject - expect(RegistryManager.findOne).to.have.been.calledWith({ - id: data.registryId, - }, transaction) - }) - - context('when RegistryManager#findOne() fails', () => { - def('registryFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when RegistryManager#findOne() succeeds', () => { - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject - const whereFind = catalogItem.id - ? { - [Op.or]: [{ userId: catalogItem.userId }, { userId: null }], - name: data.name, - id: { [Op.ne]: catalogItem.id }, - } - : { [Op.or]: [{ userId: catalogItem.userId }, { userId: null }], name: data.name } - expect(CatalogItemManager.findOne).to.have.been.calledWith(whereFind, transaction) - }) - - context('when CatalogItemManager#findOne() succeeds', () => { - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject - const whereFind = catalogItem.id - ? { - [Op.or]: [{ userId: catalogItem.userId }, { userId: null }], - name: data.name, - id: { [Op.ne]: catalogItem.id }, - } - : { [Op.or]: [{ userId: catalogItem.userId }, { userId: null }], name: data.name } - expect(CatalogItemManager.findOne).to.have.been.calledWith(whereFind, transaction) - }) - - context('when CatalogItemManager#findOne() succeeds', () => { - it('calls CatalogItemManager#update() with correct args', async () => { - await $subject - expect(CatalogItemManager.update).to.have.been.calledWith(where, catalogItem, transaction) - }) - - context('when CatalogItemManager#update() fails', () => { - def('catalogItemUpdateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemManager#update() succeeds', () => { - it('calls CatalogItemImageManager#updateOrCreate() with correct args', async () => { - await $subject - expect(CatalogItemImageManager.updateOrCreate).to.have.been.calledWith({ - catalogItemId: data.id, - archId: image1.archId, - }, { - catalogItemId: data.id, - archId: image1.archId, - containerImage: updatedImage1.containerImage, - }, transaction) - }) - - context('when CatalogItemImageManager#updateOrCreate() fails', () => { - def('catalogItemImageUpdateOrCreateResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when CatalogItemImageManager#updateOrCreate() succeeds', () => { - it('calls CatalogItemImageManager#updateOrCreate() with correct args', async () => { - await $subject - expect(CatalogItemImageManager.updateOrCreate).to.have.been.calledWith({ - catalogItemId: id, - archId: image2.archId, - }, { - catalogItemId: id, - archId: image2.archId, - containerImage: updatedImage2.containerImage, - }, transaction) - }) - - context('when CatalogItemImageManager#updateOrCreate() fails', () => { - def('catalogItemImageUpdateOrCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemImageManager#updateOrCreate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(catalogItemInputType) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse2', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls CatalogItemInputTypeManager#updateOrCreate() with correct args', async () => { - await $subject - expect(CatalogItemInputTypeManager.updateOrCreate).to.have.been.calledWith({ - catalogItemId: data.id, - }, catalogItemInputType, transaction) - }) - - context('when CatalogItemInputTypeManager#updateOrCreate() fails', () => { - def('catalogItemInputTypeUpdateOrCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemInputTypeManager#updateOrCreate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(catalogItemOutputType) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse3', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls CatalogItemOutputTypeManager#updateOrCreate() with correct args', async () => { - await $subject - expect(CatalogItemOutputTypeManager.updateOrCreate).to.have.been.calledWith({ - catalogItemId: data.id, - }, catalogItemOutputType, transaction) - }) - - context('when CatalogItemOutputTypeManager#updateOrCreate() fails', () => { - def('catalogItemOutputTypeUpdateOrCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemOutputTypeManager#updateOrCreate() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) describe('.listCatalogItemsEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const isCLI = false - - const where = isCLI - ? { [Op.or]: [{ category: { [Op.ne]: 'SYSTEM' } }, { category: null }] } - : { - [Op.or]: [{ userId: user.id }, { userId: null }], - [Op.or]: [{ category: { [Op.ne]: 'SYSTEM' } }, { category: null }], - } - - const attributes = isCLI - ? {} - : { exclude: ['userId'] } + const items = [buildCatalogItem()] - def('subject', () => $subject.listCatalogItemsEndPoint(user, isCLI, transaction)) - - def('catalogItemsFindResponse', () => Promise.resolve()) + def('subject', () => $service.listCatalogItemsEndPoint(isCLI, transaction)) beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findAllWithDependencies').returns($catalogItemFindResponse) + $sandbox.stub(CatalogItemManager, 'findAllWithDependencies').resolves(items) }) - it('calls CatalogItemManager#findAllWithDependencies() with correct args', async () => { - await $subject - expect(CatalogItemManager.findAllWithDependencies).to.have.been.calledWith(where, attributes, transaction) - }) - - context('when CatalogItemManager#findAllWithDependencies() fails', () => { - def('catalogItemsFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.eventually.have.property('catalogItems') - }) - }) - - context('when CatalogItemManager#findAllWithDependencies() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.have.property('catalogItems') - }) + it('returns catalog items', async () => { + const result = await $subject + expect(CatalogItemManager.findAllWithDependencies).to.have.been.calledWith({}, {}, transaction) + expect(result.catalogItems).to.equal(items) }) }) describe('.getCatalogItemEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const isCLI = false + const item = buildCatalogItem() - const id = 5 - - const where = isCLI - ? { id: id } - : { - id: id, - [Op.or]: [{ userId: user.id }, { userId: null }], - [Op.or]: [{ category: { [Op.ne]: 'SYSTEM' } }, { category: null }], - } - - const attributes = isCLI - ? {} - : { exclude: ['userId'] } - - def('subject', () => $subject.getCatalogItemEndPoint(id, user, isCLI, transaction)) - - def('catalogItemFindResponse', () => Promise.resolve({})) + def('subject', () => $service.getCatalogItemEndPoint(item.id, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOneWithDependencies').returns($catalogItemFindResponse) + $sandbox.stub(CatalogItemManager, 'findOneWithDependencies').resolves(item) }) - it('calls CatalogItemManager#findOneWithDependencies() with correct args', async () => { - await $subject - expect(CatalogItemManager.findOneWithDependencies).to.have.been.calledWith(where, attributes, transaction) + it('returns a catalog item by id', async () => { + const result = await $subject + expect(CatalogItemManager.findOneWithDependencies).to.have.been.calledWith({ id: item.id }, {}, transaction) + expect(result).to.equal(item) }) - context('when CatalogItemManager#findOneWithDependencies() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when item is missing', () => { + beforeEach(() => { + CatalogItemManager.findOneWithDependencies.resolves(null) }) - }) - context('when CatalogItemManager#findOneWithDependencies() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal({}) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) describe('.deleteCatalogItemEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const isCLI = false - - const id = 5 + const itemId = 15 + const item = buildCatalogItem({ id: itemId }) - const where = isCLI - ? { id: id } - : { userId: user.id, id: id } - - def('subject', () => $subject.deleteCatalogItemEndPoint(id, user, isCLI, transaction)) - - def('catalogItemFindResponse', () => Promise.resolve({})) - def('response', () => 1) - def('catalogItemDeleteResponse', () => Promise.resolve($response)) + def('subject', () => $service.deleteCatalogItemEndPoint(itemId, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) - $sandbox.stub(CatalogItemManager, 'delete').returns($catalogItemDeleteResponse) - }) - - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject - whereFind = isCLI - ? { - id: id, - } - : { - userId: user.id, - id: id, - } - expect(CatalogItemManager.findOne).to.have.been.calledWith(whereFind, transaction) + $sandbox.stub(CatalogItemManager, 'findOne').resolves(item) + $sandbox.stub(MicroserviceManager, 'findAllWithStatuses').resolves([]) + $sandbox.stub(CatalogItemManager, 'delete').resolves(1) }) - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('deletes an unused catalog item', async () => { + const result = await $subject + expect(CatalogItemManager.delete).to.have.been.calledWith({ id: itemId }, transaction) + expect(result).to.equal(1) }) - context('when CatalogItemManager#findOne() succeeds', () => { - it('calls CatalogItemManager#delete() with correct args', async () => { - await $subject - expect(CatalogItemManager.delete).to.have.been.calledWith(where, transaction) + context('when item is system catalog', () => { + beforeEach(() => { + CatalogItemManager.findOne.resolves({ ...item, category: 'SYSTEM' }) }) - context('when CatalogItemManager#delete() fails', () => { - def('catalogItemDeleteResponse', () => Promise.reject(error)) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) + }) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + context('when item is in use', () => { + beforeEach(() => { + MicroserviceManager.findAllWithStatuses.resolves([{ uuid: 'msvc-uuid' }]) }) - context('when CatalogItemManager#delete() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal($response) - }) - }) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(ErrorMessages.CATALOG_ITEM_IMAGES_IS_FROZEN)) }) }) - describe('.getNetworkCatalogItem()', () => { - const transaction = {} - const error = 'Error!' - - def('subject', () => $subject.getNetworkCatalogItem(transaction)) + describe('.updateCatalogItemEndPoint()', () => { + const itemId = 15 + const existing = buildCatalogItem({ id: itemId }) + const updateData = { description: 'updated description' } - def('response', () => 1) - def('catalogItemFindResponse', () => Promise.resolve($response)) + def('subject', () => $service.updateCatalogItemEndPoint(itemId, updateData, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + $sandbox.stub(AppHelper, 'isEmpty').returns(false) + $sandbox.stub(CatalogItemManager, 'findOne').resolves(existing) + $sandbox.stub(CatalogItemManager, 'update').resolves() }) - it('calls CatalogItemManager#findOne() with correct args', async () => { + it('updates catalog item metadata', async () => { await $subject - expect(CatalogItemManager.findOne).to.have.been.calledWith({ - name: 'Networking Tool', - category: 'SYSTEM', - publisher: 'Eclipse ioFog', - registry_id: 1, - user_id: null, - }, transaction) + expect(CatalogItemManager.update).to.have.been.calledWith({ id: itemId }, sinon.match.object, transaction) }) - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when item is system catalog', () => { + beforeEach(() => { + CatalogItemManager.findOne.resolves({ ...existing, category: 'SYSTEM' }) }) - }) - context('when CatalogItemManager#findOne() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal($response) - }) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) }) - }) - describe('.getRouterCatalogItem()', () => { - const transaction = {} - const error = 'Error!' - - def('subject', () => $subject.getRouterCatalogItem(transaction)) - - def('response', () => 1) - def('catalogItemFindResponse', () => Promise.resolve($response)) - - beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) - }) - - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject - expect(CatalogItemManager.findOne).to.have.been.calledWith({ - name: 'Router', - category: 'SYSTEM', - publisher: 'Eclipse ioFog', - registry_id: 1, - user_id: null, - }, transaction) - }) + context('when images are updated for in-use catalog item', () => { + const microservice = { uuid: 'msvc-uuid', iofogUuid: 'fog-uuid' } + const dataWithImages = { + description: 'updated', + images: [{ containerImage: 'demo:v2', archId: 1 }] + } - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) + def('subject', () => $service.updateCatalogItemEndPoint(itemId, dataWithImages, isCLI, transaction)) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + beforeEach(() => { + $sandbox.stub(CatalogItemImageManager, 'updateOrCreate').resolves() + $sandbox.stub(MicroserviceManager, 'findAllWithStatuses').resolves([microservice]) + $sandbox.stub(MicroserviceManager, 'updateAndFind').resolves(microservice) + $sandbox.stub(ChangeTrackingService, 'update').resolves() }) - }) - context('when CatalogItemManager#findOne() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal($response) + it('marks microservices for rebuild', async () => { + await $subject + expect(MicroserviceManager.updateAndFind).to.have.been.calledWith( + { uuid: microservice.uuid }, + { rebuild: true }, + transaction + ) + expect(ChangeTrackingService.update).to.have.been.calledWith( + microservice.iofogUuid, + ChangeTrackingService.events.microserviceCommon, + transaction + ) }) }) }) - describe('.getProxyCatalogItem()', () => { - const transaction = {} - const error = 'Error!' - - def('subject', () => $subject.getProxyCatalogItem(transaction)) - - def('response', () => 1) - def('catalogItemFindResponse', () => Promise.resolve($response)) - + describe('system catalog item lookups', () => { beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) + $sandbox.stub(CatalogItemManager, 'findOne').resolves(buildCatalogItem({ category: 'SYSTEM' })) }) - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject + it('.getNatsCatalogItem() queries NATs system item', async () => { + await CatalogService.getNatsCatalogItem(transaction) expect(CatalogItemManager.findOne).to.have.been.calledWith({ - name: 'Proxy', + name: 'NATs', category: 'SYSTEM', - publisher: 'Eclipse ioFog', - registry_id: 1, - user_id: null, + publisher: 'Datasance', + registry_id: 1 }, transaction) }) - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemManager#findOne() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal($response) - }) + it('.getRouterCatalogItem() queries router system item', async () => { + await CatalogService.getRouterCatalogItem(transaction) + expect(CatalogItemManager.findOne).to.have.been.calledWith({ + name: DBConstants.ROUTER_CATALOG_NAME, + category: 'SYSTEM', + publisher: 'Datasance', + registry_id: 1 + }, transaction) }) - }) - - describe('.getBluetoothCatalogItem()', () => { - const transaction = {} - const error = 'Error!' - - def('subject', () => $subject.getBluetoothCatalogItem(transaction)) - def('response', () => 1) - def('catalogItemFindResponse', () => Promise.resolve($response)) - - beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) + it('.getDebugCatalogItem() queries debug system item', async () => { + await CatalogService.getDebugCatalogItem(transaction) + expect(CatalogItemManager.findOne).to.have.been.calledWith({ + name: DBConstants.DEBUG_CATALOG_NAME, + category: 'SYSTEM', + publisher: 'Datasance', + registry_id: 1 + }, transaction) }) - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject + it('.getBluetoothCatalogItem() queries RESTBlue system item', async () => { + await CatalogService.getBluetoothCatalogItem(transaction) expect(CatalogItemManager.findOne).to.have.been.calledWith({ name: 'RESTBlue', category: 'SYSTEM', - publisher: 'Eclipse ioFog', - registry_id: 1, - user_id: null, + publisher: 'Datasance', + registry_id: 1 }, transaction) }) - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemManager#findOne() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal($response) - }) - }) - }) - - describe('.getHalCatalogItem()', () => { - const transaction = {} - const error = 'Error!' - - def('subject', () => $subject.getHalCatalogItem(transaction)) - - def('response', () => 1) - def('catalogItemFindResponse', () => Promise.resolve($response)) - - beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) - }) - - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject + it('.getHalCatalogItem() queries HAL system item', async () => { + await CatalogService.getHalCatalogItem(transaction) expect(CatalogItemManager.findOne).to.have.been.calledWith({ name: 'HAL', category: 'SYSTEM', - publisher: 'Eclipse ioFog', - registry_id: 1, - user_id: null, + publisher: 'Datasance', + registry_id: 1 }, transaction) }) - - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemManager#findOne() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal($response) - }) - }) }) }) diff --git a/test/src/services/iofog-service.test.js b/test/src/services/iofog-service.test.js index 90ad224f..08406a18 100644 --- a/test/src/services/iofog-service.test.js +++ b/test/src/services/iofog-service.test.js @@ -7,1978 +7,529 @@ const RouterManager = require('../../../src/data/managers/router-manager') const RouterConnectionManager = require('../../../src/data/managers/router-connection-manager') const RouterService = require('../../../src/services/router-service') const NatsService = require('../../../src/services/nats-service') +const NatsInstanceManager = require('../../../src/data/managers/nats-instance-manager') +const NatsConnectionManager = require('../../../src/data/managers/nats-connection-manager') const AppHelper = require('../../../src/helpers/app-helper') const Validator = require('../../../src/schemas') const ChangeTrackingService = require('../../../src/services/change-tracking-service') const CatalogService = require('../../../src/services/catalog-service') const MicroserviceManager = require('../../../src/data/managers/microservice-manager') -const MicroserviceExtraHostManager = require('../../../src/data/managers/microservice-extra-host-manager') +const MicroserviceService = require('../../../src/services/microservices-service') +const ApplicationManager = require('../../../src/data/managers/application-manager') +const SecretManager = require('../../../src/data/managers/secret-manager') +const FogPublicKeyManager = require('../../../src/data/managers/iofog-public-key-manager') +const TagsManager = require('../../../src/data/managers/tags-manager') const ioFogProvisionKeyManager = require('../../../src/data/managers/iofog-provision-key-manager') const ioFogVersionCommandManager = require('../../../src/data/managers/iofog-version-command-manager') const HWInfoManager = require('../../../src/data/managers/hw-info-manager') const USBInfoManager = require('../../../src/data/managers/usb-info-manager') const Errors = require('../../../src/helpers/errors') -const Op = require('sequelize').Op -const constants = require('../../../src/helpers/constants') + +const isCLI = false +const transaction = {} + +function buildFogModel (fields = {}) { + const fog = { + tags: [], + name: 'test-fog', + uuid: 'testUuid', + archId: 1, + host: '1.2.3.4', + isSystem: false, + routerId: null, + ...fields + } + fog.getRouter = fields.getRouter || (() => Promise.resolve(null)) + fog.getNats = fields.getNats || (() => Promise.resolve(null)) + fog.getVolumeMounts = fields.getVolumeMounts || (() => Promise.resolve([])) + fog.setTags = fields.setTags || sinon.stub().resolves() + fog.toJSON = function toJSON () { + const { getRouter, getNats, getVolumeMounts, setTags, toJSON, ...json } = this + return json + } + return fog +} + +function stubFogReadDeps (sandbox) { + sandbox.stub(NatsInstanceManager, 'findOne').resolves(null) + sandbox.stub(NatsConnectionManager, 'findAllWithNats').resolves([]) + sandbox.stub(RouterConnectionManager, 'findAllWithRouters').resolves([]) + const routerFind = sandbox.stub(RouterManager, 'findOne').resolves(null) + routerFind.withArgs({ isDefault: true }).resolves({ + id: 99, + isDefault: true, + iofogUuid: 'default-router' + }) +} + +function stubCreateFogDeps (sandbox, { uuid = 'testUuid', existingFogs = [{ uuid: 'existing' }] } = {}) { + delete process.env.CONTROL_PLANE + sandbox.stub(Validator, 'validate').resolves(true) + sandbox.stub(AppHelper, 'generateUUID').returns(uuid) + sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + sandbox.stub(ioFogManager, 'findAll').resolves(existingFogs) + sandbox.stub(ioFogManager, 'findOne').resolves(null) + sandbox.stub(ioFogManager, 'create').callsFake((data) => Promise.resolve(buildFogModel({ ...data, uuid }))) + sandbox.stub(ioFogManager, 'update').resolves() + sandbox.stub(RouterManager, 'findOne').resolves({ id: 1, isDefault: true }) + sandbox.stub(RouterService, 'validateAndReturnUpstreamRouters').resolves([]) + sandbox.stub(RouterService, 'createRouterForFog').resolves() + sandbox.stub(RouterService, 'getNetworkRouter').resolves({ id: 2, host: 'localhost', messagingPort: 5671 }) + sandbox.stub(NatsService, 'ensureNatsForFog').resolves() + sandbox.stub(ChangeTrackingService, 'create').resolves() + sandbox.stub(ChangeTrackingService, 'update').resolves() + sandbox.stub(TagsManager, 'findOne').resolves(null) + sandbox.stub(TagsManager, 'create').callsFake(({ value }) => Promise.resolve({ value })) + sandbox.stub(ioFogService, '_handleRouterCertificates').resolves() +} + +function stubUpdateFogDeps (sandbox, oldFog) { + delete process.env.CONTROL_PLANE + sandbox.stub(Validator, 'validate').resolves(true) + sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + sandbox.stub(ioFogManager, 'findOne').resolves(oldFog) + sandbox.stub(ioFogManager, 'update').resolves() + sandbox.stub(RouterConnectionManager, 'findAllWithRouters').resolves([]) + sandbox.stub(RouterManager, 'findOne').resolves({ id: 1, isDefault: true }) + sandbox.stub(RouterService, 'validateAndReturnUpstreamRouters').resolves([]) + sandbox.stub(RouterService, 'createRouterForFog').resolves() + sandbox.stub(RouterService, 'updateRouter').resolves() + sandbox.stub(RouterService, 'getNetworkRouter').resolves({ id: 2, host: 'localhost' }) + sandbox.stub(NatsService, 'ensureNatsForFog').resolves() + sandbox.stub(NatsService, 'cleanupNatsForFog').resolves() + sandbox.stub(ChangeTrackingService, 'update').resolves() + sandbox.stub(TagsManager, 'findOne').resolves(null) + sandbox.stub(TagsManager, 'create').callsFake(({ value }) => Promise.resolve({ value })) + sandbox.stub(ioFogService, '_handleRouterCertificates').resolves() +} describe('ioFog Service', () => { def('subject', () => ioFogService) def('sandbox', () => sinon.createSandbox()) - const isCLI = false - - afterEach(() => $sandbox.restore()) + afterEach(() => { + delete process.env.CONTROL_PLANE + $sandbox.restore() + }) describe('.createFogEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const date = 155555555 - const uuid = 'testUuid' - const uuid2 = 'testUuid2' - const uuid3 = 'testUuid3' - const fogData = { name: 'testName', - location: 'testLocation', - latitude: 45, - longitude: 46, - description: 'testDescription', - containerEngineUrl: 'testContainerEngineUrl', - diskLimit: 15, - diskDirectory: 'testDirectory', - memoryLimit: 55, - cpuLimit: 56, - logLimit: 57, - logDirectory: 'testLogDirectory', - logFileCount: 23, - statusFrequency: 24, - changeFrequency: 25, - deviceScanFrequency: 26, - bluetoothEnabled: true, - watchdogEnabled: false, - abstractedHardwareEnabled: true, + host: '1.2.3.4', archId: 1, + containerEngineUrl: 'unix:///var/run/docker.sock', pruningFrequency: 10, availableDiskThreshold: 20, logLevel: 'INFO', - isSystem: false, - host: '1.2.3.4', - timeZone: '', - } - - const createFogData = { - uuid: uuid, - name: fogData.name, - location: fogData.location, - latitude: fogData.latitude, - longitude: fogData.longitude, - gpsMode: fogData.latitude || fogData.longitude ? 'manual' : undefined, - description: fogData.description, - dockerUrl: fogData.containerEngineUrl, - diskLimit: fogData.diskLimit, - diskDirectory: fogData.diskDirectory, - memoryLimit: fogData.memoryLimit, - cpuLimit: fogData.cpuLimit, - logLimit: fogData.logLimit, - logDirectory: fogData.logDirectory, - logFileCount: fogData.logFileCount, - statusFrequency: fogData.statusFrequency, - changeFrequency: fogData.changeFrequency, - deviceScanFrequency: fogData.deviceScanFrequency, - bluetoothEnabled: fogData.bluetoothEnabled, - watchdogEnabled: fogData.watchdogEnabled, - abstractedHardwareEnabled: fogData.abstractedHardwareEnabled, - archId: fogData.archId, - isSystem: fogData.isSystem, - dockerPruningFrequency: fogData.pruningFrequency, - availableDiskThreshold: 20, - logLevel: 'INFO', - routerId: null, - host: '1.2.3.4', - timeZone: '', - } - - const halItem = { - id: 10, - } - - const oldFog = null - const halMicroserviceData = { - uuid: uuid2, - name: `Hal for Fog ${createFogData.uuid}`, - config: '{}', - catalogItemId: halItem.id, - iofogUuid: createFogData.uuid, - rootHostAccess: true, - logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId: oldFog ? oldFog.userId : undefined, - configLastUpdated: date, - } - - - const bluetoothItem = { - id: 10, - } - const bluetoothMicroserviceData = { - uuid: uuid3, - name: `Bluetooth for Fog ${createFogData.uuid}`, - config: '{}', - catalogItemId: bluetoothItem.id, - iofogUuid: createFogData.uuid, - rootHostAccess: true, - logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId: oldFog ? oldFog.userId : undefined, - configLastUpdated: date, - } - - - const response = { - uuid: uuid, - } - - const networkRouter = { - uuid: 'fakeUuid', - host: 'localhost', - messagingPort: 5672, - id: 2 - } - - const router = { - isEdge: true, - ...networkRouter, - iofogUuid: uuid, - id: 1 + routerMode: 'edge', + abstractedHardwareEnabled: false, + bluetoothEnabled: false } def('subject', () => $subject.createFogEndPoint(fogData, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('generateUuidResponse', () => uuid) - def('generateRandomStringResponse2', () => uuid2) - def('generateRandomStringResponse3', () => uuid3) - def('deleteUndefinedFieldsResponse', () => createFogData) - def('createIoFogResponse', () => Promise.resolve(response)) - def('createChangeTrackingResponse', () => Promise.resolve()) - def('getHalCatalogItemResponse', () => Promise.resolve(halItem)) - def('createMicroserviceResponse', () => Promise.resolve()) - def('createMicroserviceResponse2', () => Promise.resolve()) - def('getBluetoothCatalogItemResponse', () => Promise.resolve(bluetoothItem)) - def('updateChangeTrackingResponse', () => Promise.resolve()) - - def('getNetworkRouterResponse', () => Promise.resolve(networkRouter)) - def('findOneRouterResponse', () => Promise.resolve(router)) - def('emptyUpstreamRouters', () => Promise.resolve([])) - - def('dateResponse', () => date) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'generateUUID').returns($generateUuidResponse) - $sandbox.stub(AppHelper, 'generateRandomString') - .onFirstCall().returns($generateRandomStringResponse2) - .onSecondCall().returns($generateRandomStringResponse3) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(ioFogManager, 'create').returns($createIoFogResponse) - $sandbox.stub(ChangeTrackingService, 'create').returns($createChangeTrackingResponse) - $sandbox.stub(CatalogService, 'getHalCatalogItem').returns($getHalCatalogItemResponse) - $sandbox.stub(MicroserviceManager, 'create') - .onFirstCall().returns($createMicroserviceResponse) - .onSecondCall().returns($createMicroserviceResponse2) - $sandbox.stub(CatalogService, 'getBluetoothCatalogItem').returns($getBluetoothCatalogItemResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - - $sandbox.stub(RouterService, 'getNetworkRouter').returns($getNetworkRouterResponse) - $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) - $sandbox.stub(RouterService, 'validateAndReturnUpstreamRouters').returns($emptyUpstreamRouters) - $sandbox.stub(RouterService, 'createRouterForFog').returns($findOneRouterResponse) - $sandbox.stub(NatsService, 'ensureNatsForFog').returns(Promise.resolve()) - $sandbox.stub(ioFogManager, 'update').returns($createIoFogResponse) - $sandbox.stub(ioFogManager, 'findOne').returns(Promise.resolve()) - $sandbox.stub(ioFogManager, 'findAll').returns(Promise.resolve([{ uuid: 'existing-fog' }])) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns(Promise.resolve()) - - $sandbox.stub(Date, 'now').returns($dateResponse) + stubCreateFogDeps($sandbox, { uuid }) }) - it('calls Validator#validate() with correct args', async () => { + it('validates input', async () => { await $subject expect(Validator.validate).to.have.been.calledWith(fogData, Validator.schemas.iofogCreate) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + it('creates the fog and returns uuid immediately', async () => { + const result = await $subject + expect(result).to.eql({ uuid }) + expect(ioFogManager.create).to.have.been.calledOnce + const createPayload = ioFogManager.create.firstCall.args[0] + expect(createPayload).to.include({ + name: fogData.name, + containerEngineUrl: fogData.containerEngineUrl, + pruningFrequency: fogData.pruningFrequency }) }) - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#generateUUID()', async () => { - await $subject - expect(AppHelper.generateUUID).to.have.been.called - }) + it('does not run HAL/Bluetooth catalog work on the synchronous path', async () => { + $sandbox.stub(CatalogService, 'getHalCatalogItem').resolves({ id: 1 }) + $sandbox.stub(CatalogService, 'getBluetoothCatalogItem').resolves({ id: 2 }) + await $subject + expect(CatalogService.getHalCatalogItem).to.not.have.been.called + expect(CatalogService.getBluetoothCatalogItem).to.not.have.been.called + }) - context('when AppHelper#generateUUID() fails', () => { - def('generateUuidResponse', () => { throw error }) + context('when validation fails', () => { + const validationError = new Error('validation failed') - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + beforeEach(() => { + Validator.validate.restore() + $sandbox.stub(Validator, 'validate').rejects(validationError) }) - context('when AppHelper#generateUUID() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(createFogData) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls ioFogManager#create() with correct args', async () => { - await $subject - - expect(ioFogManager.create).to.have.been.calledWith(createFogData) - }) - - context('when ioFogManager#create() fails', () => { - def('createIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#create() succeeds', () => { - it('calls ChangeTrackingService#create() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.create).to.have.been.calledWith(uuid, transaction) - }) - - context('when ChangeTrackingService#create() fails', () => { - def('createIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#create() succeeds', () => { - it('calls CatalogService#getHalCatalogItem() with correct args', async () => { - await $subject - - expect(CatalogService.getHalCatalogItem).to.have.been.calledWith(transaction) - }) - - context('when CatalogService#getHalCatalogItem() fails', () => { - def('getHalCatalogItemResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) + it('rejects', () => expect($subject).to.be.rejectedWith(validationError)) + }) - context('when CatalogService#getHalCatalogItem() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject + context('when name already exists', () => { + beforeEach(() => { + ioFogManager.findOne.withArgs({ name: fogData.name }).resolves({ uuid: 'other' }) + }) - expect(AppHelper.generateRandomString).to.have.been.calledWith(32) - }) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) + }) - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse2', () => error) + context('when routerMode is none', () => { + beforeEach(() => { + fogData.routerMode = 'none' + fogData.networkRouter = 'default-router' + }) - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) + afterEach(() => { + fogData.routerMode = 'edge' + delete fogData.networkRouter + }) - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls MicroserviceManager#create() with correct args', async () => { - await $subject + it('resolves network router and stores routerId', async () => { + await $subject + expect(RouterService.getNetworkRouter).to.have.been.calledWith('default-router') + const createPayload = ioFogManager.create.firstCall.args[0] + expect(createPayload.routerId).to.equal(2) + }) - expect(MicroserviceManager.create).to.have.been.calledWith(halMicroserviceData, transaction) - }) + context('when network router is missing', () => { + beforeEach(() => { + RouterService.getNetworkRouter.resolves(null) + }) - context('when MicroserviceManager#create() fails', () => { - def('createMicroserviceResponse', () => Promise.reject(error)) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) + }) + }) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#create() succeeds', () => { - it('calls CatalogService#getBluetoothCatalogItem() with correct args', async () => { - await $subject - - expect(CatalogService.getBluetoothCatalogItem).to.have.been.calledWith(transaction) - }) - - context('when CatalogService#getBluetoothCatalogItem() fails', () => { - def('getBluetoothCatalogItemResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogService#getBluetoothCatalogItem() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - - expect(AppHelper.generateRandomString).to.have.been.calledWith(32) - }) - - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse3', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) - - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls MicroserviceManager#create() with correct args', async () => { - await $subject - - expect(MicroserviceManager.create).to.have.been.calledWith(bluetoothMicroserviceData, transaction) - }) - - context('when MicroserviceManager#create() fails', () => { - def('createMicroserviceResponse2', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#create() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.update).to.have.been.calledWith(createFogData.uuid, - ChangeTrackingService.events.microserviceCommon, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - - context('when routerMode is none', () => { - const networkRouterUuid = 'fakeUuid' - - beforeEach(() => { - fogData.routerMode = 'none' - fogData.networkRouter = networkRouterUuid - }) - - afterEach(() => { - delete fogData.routerMode - delete fogData.networkRouter - }) - - it('calls RouterService.getNetworkRouter with correct args', async () => { - await $subject - - expect(RouterService.getNetworkRouter).to.have.been.calledWith(networkRouterUuid) - }) - - context('When there is no network router', async () => { - def('getNetworkRouterResponse', () => Promise.resolve(null)) - - it(`fails with error not found`, async () => { - try { - await $subject - return expect(true).to.be.false() - } catch (e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - }) - }) - - context('When there is a network router', async () => { - it('Should use create the fog with the network router', async () => { - await $subject - return expect(ioFogManager.create).to.have.been.calledWith({...createFogData, routerId: networkRouter.id}) - }) - }) - }) - - context('when routerMode is edge or interior', () => { - it('expects router to be created', async () => { - await $subject - return expect(RouterService.createRouterForFog).to.have.been.calledWith({...fogData, routerMode: 'edge'}, response.uuid, []) - }) - }) - }) + context('when routerMode is edge', () => { + it('validates upstream routers for router creation', async () => { + await $subject + expect(RouterService.validateAndReturnUpstreamRouters).to.have.been.called }) }) }) describe('.updateFogEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const date = 155555555 - const uuid = 'testUuid' - const uuid2 = 'testUuid2' - const uuid3 = 'testUuid3' - + const router = { + id: 1, + isEdge: true, + messagingPort: 5671, + host: '1.2.3.4', + iofogUuid: uuid + } + const oldFog = buildFogModel({ + uuid, + name: 'immutable-name', + host: '1.2.3.4', + isSystem: false, + getRouter: () => Promise.resolve(router) + }) const fogData = { - uuid: uuid, - name: 'new-name', - location: 'testLocation', - latitude: 45, - longitude: 46, - description: 'testDescription', - containerEngineUrl: 'testContainerEngineUrl', - diskLimit: 15, - diskDirectory: 'testDirectory', - memoryLimit: 55, - cpuLimit: 56, - logLimit: 57, - logDirectory: 'testLogDirectory', - logFileCount: 23, - statusFrequency: 24, - changeFrequency: 25, - deviceScanFrequency: 26, - bluetoothEnabled: true, - watchdogEnabled: false, - abstractedHardwareEnabled: true, - archId: 1, - pruningFrequency: 90, - availableDiskThreshold: 10, - logLevel: 'INFO', - isSystem: true, + uuid, + location: 'updated-location', host: '5.6.7.8', - timeZone: 'America/Los_Angeles', - } - - const oldFog = { - uuid: uuid2, - name: 'old-name', - location: 'testLocation', - latitude: 45, - longitude: 46, - description: 'testDescription', - containerEngineUrl: 'testContainerEngineUrl', - diskLimit: 15, - diskDirectory: 'testDirectory', - memoryLimit: 55, - cpuLimit: 56, - logLimit: 57, - logDirectory: 'testLogDirectory', - logFileCount: 23, - statusFrequency: 24, - changeFrequency: 25, - deviceScanFrequency: 26, - bluetoothEnabled: false, - watchdogEnabled: false, - abstractedHardwareEnabled: false, archId: 1, + containerEngineUrl: 'unix:///var/run/docker.sock', pruningFrequency: 90, - availableDiskThreshold: 10, - logLevel: 'INFO', - isSystem: false, - host: fogData.host, - userId: user.id, - timeZone: 'America/Los_Angeles', - } - - const queryFogData = { uuid: fogData.uuid } - - const updateFogData = { - name: fogData.name, - location: fogData.location, - latitude: fogData.latitude, - longitude: fogData.longitude, - gpsMode: fogData.latitude || fogData.longitude ? 'manual' : undefined, - description: fogData.description, - dockerUrl: fogData.containerEngineUrl, - diskLimit: fogData.diskLimit, - diskDirectory: fogData.diskDirectory, - memoryLimit: fogData.memoryLimit, - cpuLimit: fogData.cpuLimit, - logLimit: fogData.logLimit, - logDirectory: fogData.logDirectory, - logFileCount: fogData.logFileCount, - statusFrequency: fogData.statusFrequency, - changeFrequency: fogData.changeFrequency, - deviceScanFrequency: fogData.deviceScanFrequency, - bluetoothEnabled: fogData.bluetoothEnabled, - watchdogEnabled: fogData.watchdogEnabled, - abstractedHardwareEnabled: fogData.abstractedHardwareEnabled, - archId: fogData.archId, - dockerPruningFrequency: fogData.pruningFrequency, - availableDiskThreshold: 10, - logLevel: 'INFO', - isSystem: fogData.isSystem, - host: fogData.host, - timeZone: fogData.timeZone, - } - - const halItem = { - id: 10, - } - const halMicroserviceData = { - uuid: uuid2, - name: `Hal for Fog ${fogData.uuid}`, - config: '{}', - catalogItemId: halItem.id, - iofogUuid: fogData.uuid, - rootHostAccess: true, - logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId: oldFog ? oldFog.userId : undefined, - configLastUpdated: date, - } - - - const bluetoothItem = { - id: 10, - } - const bluetoothMicroserviceData = { - uuid: uuid3, - name: `Bluetooth for Fog ${fogData.uuid}`, - config: '{}', - catalogItemId: bluetoothItem.id, - iofogUuid: fogData.uuid, - rootHostAccess: true, - logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId: oldFog ? oldFog.userId : undefined, - configLastUpdated: date, - } - - const networkRouter = { - host: 'localhost', - messagingPort: 5672 - } - - const router = { - isEdge: true, - ...networkRouter, - iofogUuid: uuid, - id: 1 - } - - const defaultRouter = {...router, isDefault: true, id: 2} - - const routerCatalogItem = { - id: 42 + routerMode: 'edge' } def('subject', () => $subject.updateFogEndPoint(fogData, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse', () => ({...updateFogData})) - def('findIoFogResponse', () => Promise.resolve({...oldFog, getRouter: () => Promise.resolve(router)})) - def('updateIoFogResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse2', () => Promise.resolve()) - def('getHalCatalogItemResponse', () => Promise.resolve(halItem)) - def('generateRandomStringResponse', () => uuid2) - def('generateRandomStringResponse2', () => uuid3) - def('createMicroserviceResponse', () => Promise.resolve()) - def('createMicroserviceResponse2', () => Promise.resolve()) - def('getBluetoothCatalogItemResponse', () => Promise.resolve(bluetoothItem)) - - - def('getNetworkRouterResponse', () => Promise.resolve(networkRouter)) - def('getRouterCatalogItemResponse', () => Promise.resolve(routerCatalogItem)) - def('findOneRouterResponse', () => Promise.resolve(router)) - def('createRouterResponse', () => Promise.resolve(router)) - def('updateRouterResponse', () => Promise.resolve(router)) - def('findDefaultRouterResponse', () => Promise.resolve(defaultRouter)) - def('emptyUpstreamRouters', () => Promise.resolve([])) - def('dateResponse', () => date) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(ioFogManager, 'findOne') - .withArgs({ uuid: uuid }).returns($findIoFogResponse) - .withArgs({ name: 'new-name', uuid: { [Op.not]: 'testUuid' } }).returns(Promise.resolve()) - $sandbox.stub(ioFogManager, 'findOneWithTags') - .withArgs({ uuid: uuid }).returns($findIoFogResponse) - .withArgs({ name: 'new-name', uuid: { [Op.not]: 'testUuid' } }).returns(Promise.resolve()) - $sandbox.stub(ioFogManager, 'update').returns($updateIoFogResponse) - $sandbox.stub(ChangeTrackingService, 'update') - .onFirstCall().returns($updateChangeTrackingResponse) - .onSecondCall().returns($updateChangeTrackingResponse2) - $sandbox.stub(CatalogService, 'getHalCatalogItem').returns($getHalCatalogItemResponse) - $sandbox.stub(AppHelper, 'generateRandomString') - .onFirstCall().returns($generateRandomStringResponse) - .onSecondCall().returns($generateRandomStringResponse2) - $sandbox.stub(MicroserviceManager, 'create') - .onFirstCall().returns($createMicroserviceResponse) - .onSecondCall().returns($createMicroserviceResponse2) - $sandbox.stub(CatalogService, 'getBluetoothCatalogItem').returns($getBluetoothCatalogItemResponse) - - $sandbox.stub(RouterService, 'getNetworkRouter').returns($getNetworkRouterResponse) - const findRouterStub = $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) - findRouterStub.withArgs({isDefault: true}).returns($findDefaultRouterResponse) - $sandbox.stub(RouterService, 'validateAndReturnUpstreamRouters').returns($emptyUpstreamRouters) - $sandbox.stub(RouterService, 'createRouterForFog').returns($createRouterResponse) - $sandbox.stub(RouterService, 'updateRouter').returns($updateRouterResponse) - $sandbox.stub(NatsService, 'ensureNatsForFog').returns(Promise.resolve()) - $sandbox.stub(RouterConnectionManager, 'findAllWithRouters').returns($emptyUpstreamRouters) - $sandbox.stub(CatalogService, 'getRouterCatalogItem').returns($getRouterCatalogItemResponse) - $sandbox.stub(MicroserviceManager, 'delete') - $sandbox.stub(Date, 'now').returns($dateResponse) + stubUpdateFogDeps($sandbox, oldFog) }) - it('calls Validator#validate() with correct args', async () => { + it('validates input', async () => { await $subject expect(Validator.validate).to.have.been.calledWith(fogData, Validator.schemas.iofogUpdate) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('updates fog fields and returns uuid immediately', async () => { + const result = await $subject + expect(result).to.eql({ uuid }) + expect(ioFogManager.update).to.have.been.calledWith({ uuid }, sinon.match.has('location', 'updated-location'), transaction) + expect(ChangeTrackingService.update).to.have.been.calledWith(uuid, ChangeTrackingService.events.config, transaction) }) - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(updateFogData) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => error) + it('rejects rename attempts', () => { + const renamed = { ...fogData, name: 'new-name' } + return expect(ioFogService.updateFogEndPoint(renamed, isCLI, transaction)) + .to.be.rejectedWith('Agent Resource Name is immutable') + }) - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + context('when fog is not found', () => { + beforeEach(() => { + ioFogManager.findOne.resolves(null) }) - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - - expect(ioFogManager.findOne).to.have.been.calledWith(queryFogData, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls ioFogManager#update() with correct args', async () => { - await $subject - - expect(ioFogManager.update).to.have.been.calledWith(queryFogData, - {...updateFogData, routerId: router.id}) - }) - - context('when ioFogManager#update() fails', () => { - def('updateIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#update() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.update).to.have.been.calledWith(uuid, - ChangeTrackingService.events.config, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('calls CatalogService#getHalCatalogItem() with correct args', async () => { - await $subject - - expect(CatalogService.getHalCatalogItem).to.have.been.calledWith(transaction) - }) - - context('when CatalogService#getHalCatalogItem() fails', () => { - def('getHalCatalogItemResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogService#getHalCatalogItem() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - - expect(AppHelper.generateRandomString).to.have.been.calledWith(32) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) + }) - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse', () => error) + context('when validation fails', () => { + const validationError = new Error('validation failed') - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls MicroserviceManager#create() with correct args', async () => { - await $subject - - expect(MicroserviceManager.create).to.have.been.calledWith(halMicroserviceData, transaction) - }) - - context('when MicroserviceManager#create() fails', () => { - def('createMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#create() succeeds', () => { - it('calls CatalogService#getBluetoothCatalogItem() with correct args', async () => { - await $subject - - expect(CatalogService.getBluetoothCatalogItem).to.have.been.calledWith(transaction) - }) - - context('when CatalogService#getBluetoothCatalogItem() fails', () => { - def('getBluetoothCatalogItemResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogService#getBluetoothCatalogItem() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - - expect(AppHelper.generateRandomString).to.have.been.calledWith(32) - }) - - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse2', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls MicroserviceManager#create() with correct args', async () => { - await $subject - - expect(MicroserviceManager.create).to.have.been.calledWith(bluetoothMicroserviceData, transaction) - }) - - context('when MicroserviceManager#create() fails', () => { - def('createMicroserviceResponse2', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#create() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.update).to.have.been.calledWith(fogData.uuid, - ChangeTrackingService.events.microserviceCommon, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse2', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - - context('when host changes', () => { - def('findIoFogResponse', () => Promise.resolve({...oldFog, host: '0.0.0.0', getRouter: () => Promise.resolve(router)})) - def('findExtraHostsResponse', () => Promise.resolve([])) - - context('when there are extra hosts', () => { - const extraHosts = [{ - id: 1, - save: () => {} - }] - def('findExtraHostsResponse', () => Promise.resolve(extraHosts)) - - beforeEach(() => { - $sandbox.stub(MicroserviceExtraHostManager, 'findAll').returns($findExtraHostsResponse) - $sandbox.stub(MicroserviceExtraHostManager, 'updateOriginMicroserviceChangeTracking') - }) - - it('should update extraHosts', async () => { - await $subject - for (const e of extraHosts) { - expect(MicroserviceExtraHostManager.updateOriginMicroserviceChangeTracking).to.have.been.calledWith({...e, value: updateFogData.host}, transaction) - } - }) - }) - }) - - context('when router mode changes', () => { - context('when new router mode is none', () => { - beforeEach(() => { - fogData.routerMode = 'none' - $sandbox.stub(RouterManager, 'delete') - }) - afterEach(() => { - delete fogData.routerMode - }) - context('when old router mode was none', async () => { - def('findOneRouterResponse', () => Promise.resolve(null)) - it('should not delete any router', async () => { - await $subject - return expect(RouterManager.delete).not.to.have.been.called - }) - }) - context('when old router mode was not none', async () => { - def('findOneRouterResponse', () => Promise.resolve({...router, isEdge: true})) - def('findConnectedRoutersResponse', () => Promise.resolve([])) - beforeEach(() => { - $sandbox.stub(ioFogManager, 'findAll').returns($findConnectedRoutersResponse) - $sandbox.stub(ioFogManager, 'findAllWithTags').returns($findConnectedRoutersResponse) - }) - it('should delete previous router', async () => { - await $subject - return expect(RouterManager.delete).to.have.been.calledWith({iofogUuid: fogData.uuid}) - }) - context('when there are connected routers', () => { - const proxyCatalogItem = {id: 5} - const connectedFog = {uuid: 'connectedFogUuid'} - def('findConnectedRoutersResponse', () => Promise.resolve([connectedFog])) - def('findProxyCatalogItemResponse', () => Promise.resolve(proxyCatalogItem)) - def('findProxyMicroservicesResponse', () => Promise.resolve([])) - beforeEach(() => { - $sandbox.stub(CatalogService, 'getProxyCatalogItem').returns(() => Promise.resolve({id: 1})) - $sandbox.stub(MicroserviceManager, 'findAll').returns($findProxyMicroservicesResponse) - }) - - it('should update agent routerId', async () => { - await $subject - return expect(ioFogManager.update).to.have.been.calledWith({ uuid: connectedFog.uuid }, { routerId: defaultRouter.id }) - }) - - context('when there are proxy microservices', () => { - const proxyMsvc = {uuid: 'proxyMsvc'} - def('findProxyMicroservicesResponse', () => Promise.resolve([proxyMsvc])) - beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'updateIfChanged') - }) - it('should update microservice config and set flag', async () => { - await $subject - expect(MicroserviceManager.updateIfChanged).to.have.been.calledWith({ uuid: proxyMsvc.uuid }, { config: JSON.stringify({networkRouter: { host: defaultRouter.host, port: defaultRouter.messagingPort}}) }) - return expect(ChangeTrackingService.update).to.have.been.calledWith(connectedFog.uuid, ChangeTrackingService.events.microserviceConfig) - }) - }) - - }) - context('when there is no network router', () => { - def('getNetworkRouterResponse', () => Promise.resolve(null)) - it('should error with Notfound', async () => { - try{ - await $subject - return expect(false).to.eql(true) - } catch(e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - }) - }) - it('should set the network router', async () => { - await $subject - return expect(ioFogManager.update).to.have.been.calledWith(queryFogData, {...updateFogData, routerId: networkRouter.id}) - }) - }) - }) - context('when new router mode is edge', () => { - beforeEach(() => { - fogData.routerMode = 'edge' - fogData.messagingPort = 1234 - fogData.host = 'newHost' - }) - afterEach(() => { - delete fogData.routerMode - delete fogData.messagingPort - delete fogData.host - }) - context('when old router mode was none', async () => { - def('findOneRouterResponse', () => Promise.resolve(null)) - def('findIoFogResponse', () => Promise.resolve({...oldFog, getRouter: () => Promise.resolve(null)})) - it('Should create a router', async () => { - await $subject - return expect(RouterService.createRouterForFog).to.have.been.calledWith(fogData, oldFog.uuid, []) - }) - }) - - it('Should update the router', async () => { - await $subject - return expect(RouterService.updateRouter).to.have.been.calledWith(router, { - messagingPort: fogData.messagingPort, interRouterPort: router.interRouterPort, edgeRouterPort: router.edgeRouterPort, isEdge: true, host: fogData.host - }, []) - }) - }) - }) - }) + beforeEach(() => { + Validator.validate.restore() + $sandbox.stub(Validator, 'validate').rejects(validationError) }) + + it('rejects', () => expect($subject).to.be.rejectedWith(validationError)) }) }) describe('.deleteFogEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const uuid = 'testUuid' - - const fogData = { - uuid: uuid, - } - - const fog = { - uuid: uuid, - name: 'testName', - location: 'testLocation', - latitude: 45, - longitude: 46, - description: 'testDescription', - containerEngineUrl: 'testContainerEngineUrl', - diskLimit: 15, - diskDirectory: 'testDirectory', - daemonStatus: 'RUNNING', - memoryLimit: 55, - cpuLimit: 56, - logLimit: 57, - logDirectory: 'testLogDirectory', - logFileCount: 23, - statusFrequency: 24, - changeFrequency: 25, - lastStatusTime: 1555, - ipAddress: 'testIpAddress', - deviceScanFrequency: 26, - bluetoothEnabled: false, - watchdogEnabled: false, - abstractedHardwareEnabled: false, - archId: 1, - userId: user.id - } - - const networkRouter = { - host: 'localhost', - messagingPort: 5672 - } - - const router = { - isEdge: true, - ...networkRouter, - iofogUuid: uuid, - id: 1 - } - - const routerCatalogItem = { - id: 15 - } - - const queryFogData = { uuid: fogData.uuid } + const fogData = { uuid } + const fog = buildFogModel({ uuid, name: 'test-fog' }) def('subject', () => $subject.deleteFogEndPoint(fogData, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findIoFogResponse', () => Promise.resolve(fog)) - def('updateChangeTrackingResponse', () => Promise.resolve()) - def('findOneRouterResponse', () => Promise.resolve(router)) - def('upstreamRouters', () => Promise.resolve([])) - def('routerCatalogItem', () => Promise.resolve(routerCatalogItem)) beforeEach(() => { - // _deleteFogRouter is tested in update fog (with new router mode set to none) - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'delete') - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - const findRouterStub = $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) - findRouterStub.withArgs({isDefault: true}).returns(Promise.resolve(null)) - $sandbox.stub(RouterManager, 'delete') - $sandbox.stub(RouterConnectionManager, 'findAllWithRouters').returns($upstreamRouters) - $sandbox.stub(CatalogService, 'getRouterCatalogItem').returns($routerCatalogItem) - $sandbox.stub(MicroserviceManager, 'delete') - $sandbox.stub(MicroserviceManager, 'findAll').returns(Promise.resolve([])) - $sandbox.stub(NatsService, 'cleanupNatsForFog').returns(Promise.resolve()) - $sandbox.stub(ioFogManager, 'findAll').returns(Promise.resolve([])) - $sandbox.stub(ioFogManager, 'findAllWithTags').returns(Promise.resolve([])) - }) - - it('calls Validator#validate() with correct args', async () => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findOne').resolves(fog) + $sandbox.stub(MicroserviceManager, 'findAll').resolves([]) + $sandbox.stub(MicroserviceService, 'deleteMicroserviceWithRoutesAndPortMappings').resolves() + $sandbox.stub(ApplicationManager, 'delete').resolves() + $sandbox.stub(ChangeTrackingService, 'update').resolves() + $sandbox.stub(SecretManager, 'findOne').resolves(null) + $sandbox.stub(NatsService, 'cleanupNatsForFog').resolves() + $sandbox.stub(FogPublicKeyManager, 'findByFogUuid').resolves(null) + $sandbox.stub(ioFogManager, 'delete').resolves() + $sandbox.stub(RouterManager, 'findOne').resolves(null) + $sandbox.stub(RouterConnectionManager, 'findAllWithRouters').resolves([]) + $sandbox.stub(CatalogService, 'getRouterCatalogItem').resolves({ id: 1 }) + $sandbox.stub(MicroserviceManager, 'delete').resolves() + }) + + it('validates and deletes the fog node', async () => { await $subject expect(Validator.validate).to.have.been.calledWith(fogData, Validator.schemas.iofogDelete) + expect(ioFogManager.delete).to.have.been.calledWith({ uuid }, transaction) + expect(NatsService.cleanupNatsForFog).to.have.been.calledWith(fog, transaction) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - - expect(ioFogManager.findOne).to.have.been.calledWith(queryFogData, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + context('when fog is missing', () => { + beforeEach(() => { + ioFogManager.findOne.resolves(null) }) - context('when ioFogManager#findOne() succeeds', () => { - it('calls NatsService#cleanupNatsForFog() with correct args', async () => { - await $subject - expect(NatsService.cleanupNatsForFog).to.have.been.calledWith(fog, transaction) - }) - - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.update).to.have.been.calledWith(uuid, ChangeTrackingService.events.deleteNode, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - context('when there is no router', () => { - def('findOneRouterResponse', () => Promise.resolve(null)) - it('should not delete a router', async () => { - await $subject - return expect(RouterManager.delete).not.to.have.been.called - }) - }) - context('when there is a router', () => { - it('Should delete the router', async () => { - await $subject - return expect(RouterManager.delete).to.have.been.calledWith({iofogUuid: fogData.uuid}) - }) - context('when there are upstream and downstream routers', () => { - const upstreamRouters = [{ - dest: router, - source: { - ...router, - id: 2 - }, - id: 1 - }, { - dest: { - ...router, - id: 3 - }, - source: router, - id: 2 - }] - def('upstreamRouters', () => upstreamRouters) - - beforeEach(() => { - $sandbox.stub(RouterConnectionManager, 'delete') - $sandbox.stub(RouterService, 'updateConfig') - }) - - it('Should delete all connections', async () => { - await $subject - expect(RouterConnectionManager.delete).to.have.been.calledWith({id: 1}) - return expect(RouterConnectionManager.delete).to.have.been.calledWith({id: 2}) - }) - - it('Should update config of downstream connections', async () => { - await $subject - expect(RouterService.updateConfig).to.have.been.calledWith(upstreamRouters[0].source.id) - return expect(ChangeTrackingService.update).to.have.been.calledWith(router.iofogUuid, ChangeTrackingService.events.routerChanged) - }) - }) - }) - - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) describe('.getFog()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const uuid = 'testUuid' - - const fogData = { - uuid: uuid, - } - - const fog = { - uuid: uuid, + const fogData = { uuid } + const baseFog = buildFogModel({ + uuid, name: 'testName', location: 'testLocation', - latitude: 45, - longitude: 46, - description: 'testDescription', containerEngineUrl: 'testContainerEngineUrl', - diskLimit: 15, - diskDirectory: 'testDirectory', - daemonStatus: 'RUNNING', - memoryLimit: 55, - cpuLimit: 56, - logLimit: 57, - logDirectory: 'testLogDirectory', - logFileCount: 23, - statusFrequency: 24, - changeFrequency: 25, - lastStatusTime: 1555, - ipAddress: 'testIpAddress', - deviceScanFrequency: 26, - bluetoothEnabled: false, - watchdogEnabled: false, - abstractedHardwareEnabled: false, archId: 1, - userId: user.id, - routerMode: 'none', - tags: [] - } - - const queryFogData = { uuid: fogData.uuid } - - - const defaultRouter = { - id: 1, - isDefault: true - } + routerMode: 'none' + }) def('subject', () => $subject.getFog(fogData, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findIoFogResponse', () => Promise.resolve({...fog, getRouter: () => Promise.resolve(null), toJSON: () => fog})) - def('findOneRouterResponse', () => Promise.resolve(null)) - def('defaultRouterResponse', () => Promise.resolve(defaultRouter)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - const stub = $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) - stub.withArgs({isDefault: true}).returns($defaultRouterResponse) + stubFogReadDeps($sandbox) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findOneWithTags').resolves(baseFog) }) - it('calls Validator#validate() with correct args', async () => { - await $subject + it('validates and returns enriched fog data', async () => { + const result = await $subject expect(Validator.validate).to.have.been.calledWith(fogData, Validator.schemas.iofogGet) + expect(result.uuid).to.equal(uuid) + expect(result.name).to.equal('testName') + expect(result.routerMode).to.equal('none') + expect(result.natsMode).to.equal('none') + expect(result.tags).to.eql([]) + expect(result.volumeMounts).to.eql([]) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) + context('when fog has an edge router', () => { + const router = { id: 42, isEdge: true, messagingPort: 1234 } - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + beforeEach(() => { + ioFogManager.findOneWithTags.resolves(buildFogModel({ + ...baseFog, + getRouter: () => Promise.resolve(router) + })) }) - }) - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findOneWithTags() with correct args', async () => { - await $subject - - expect(ioFogManager.findOneWithTags).to.have.been.calledWith(queryFogData, transaction) + it('includes router config', async () => { + const result = await $subject + expect(result.routerMode).to.equal('edge') + expect(result.messagingPort).to.equal(1234) + expect(result.upstreamRouters).to.eql([]) }) + }) - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + context('when fog is not found', () => { + beforeEach(() => { + ioFogManager.findOneWithTags.resolves(null) }) - context('when ioFogManager#findOne() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal(fog) - }) - - context('when there is a router', () => { - const router = { - isEdge: true, - messagingPort: 1234, - id: 42 - } - def('findIoFogResponse', () => Promise.resolve({...fog, getRouter: () => Promise.resolve(router), toJSON: () => fog})) - def('findRouterConnectionsResponse', () => Promise.resolve([])) - beforeEach(() => { - $sandbox.stub(RouterConnectionManager, 'findAllWithRouters').returns($findRouterConnectionsResponse) - }) - - it('should return router information', () => { - return expect($subject).to.eventually.deep.equal({...fog, routerMode: 'edge', messagingPort: router.messagingPort, upstreamRouters: []}) - }) - - context('when it has upstream routers', () => { - const upstreamRouter = { - id: 43, - isEdge: false, - iofogUuid: 'upstreamFogUuid' - } - def('findRouterConnectionsResponse', () => Promise.resolve([{source: router, dest: upstreamRouter}, {source: router, dest: defaultRouter}])) - it('should have upstream router with agent uuid and default-router', () => { - return expect($subject).to.eventually.deep.equal({...fog, routerMode: 'edge', messagingPort: router.messagingPort, upstreamRouters: [upstreamRouter.iofogUuid, 'default-router']}) - }) - }) - - context('when it is an interior router', () => { - const router = { - isEdge: false, - messagingPort: 1234, - interRouterPort: 4567, - edgeRouterPort: 7890, - id: 42 - } - def('findIoFogResponse', () => Promise.resolve({...fog, getRouter: () => Promise.resolve(router), toJSON: () => fog})) - it('should return router information', () => { - return expect($subject).to.eventually.deep.equal({...fog, routerMode: 'interior', messagingPort: router.messagingPort, edgeRouterPort: router.edgeRouterPort, interRouterPort: router.interRouterPort, upstreamRouters: []}) - }) - }) - }) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) describe('.getFogListEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const uuid = 'testUuid' - - const fog = { - uuid: uuid, - name: 'testName', - location: 'testLocation', - latitude: 45, - longitude: 46, - description: 'testDescription', - containerEngineUrl: 'testContainerEngineUrl', - diskLimit: 15, - diskDirectory: 'testDirectory', - daemonStatus: 'RUNNING', - memoryLimit: 55, - cpuLimit: 56, - logLimit: 57, - logDirectory: 'testLogDirectory', - logFileCount: 23, - statusFrequency: 24, - changeFrequency: 25, - lastStatusTime: 1555, - ipAddress: 'testIpAddress', - deviceScanFrequency: 26, - bluetoothEnabled: false, - watchdogEnabled: false, - abstractedHardwareEnabled: false, - archId: 1, - } - - const isSystem = false - - const fogs = [fog] - - const queryFogData = {} - const filters = [] + const fog = buildFogModel({ uuid: 'testUuid', name: 'testName', archId: 1 }) def('subject', () => $subject.getFogListEndPoint(filters, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findAllIoFogResponse', () => Promise.resolve(fogs.map(f => ({...f, getRouter: () => Promise.resolve(null), toJSON: () => f})))) - def('findOneRouterResponse', () => Promise.resolve(null)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findAllWithTags').returns($findAllIoFogResponse) - $sandbox.stub(ioFogManager, 'findAll').returns($findAllIoFogResponse) - $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) + stubFogReadDeps($sandbox) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findAllWithTags').resolves([fog]) }) - it('calls Validator#validate() with correct args', async () => { - await $subject + it('returns enriched fog list', async () => { + const result = await $subject expect(Validator.validate).to.have.been.calledWith(filters, Validator.schemas.iofogFilters) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findAllWithTags() with correct args', async () => { - await $subject - - expect(ioFogManager.findAllWithTags).to.have.been.calledWith(queryFogData, transaction) - }) - - context('when ioFogManager#findAll() fails', () => { - def('findAllIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findAll() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('fogs') - }) - }) + expect(result.fogs).to.have.length(1) + expect(result.fogs[0]).to.include({ uuid: 'testUuid', routerMode: 'none', natsMode: 'none' }) }) }) describe('.generateProvisioningKeyEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const uuid = 'testUuid' - - const date = 155555555 - - const fogData = { - uuid: uuid, - } - - const queryFogData = { uuid: fogData.uuid } - - const provisionKey = 'tttttttt' - const expirationTime = date + (20 * 60 * 1000) - - const newProvision = { - iofogUuid: fogData.uuid, - provisionKey: provisionKey, - expirationTime: expirationTime, - } + const fogData = { uuid } + const provisionKey = 'provision-key' + const expirationTime = 155555555 + (20 * 60 * 1000) def('subject', () => $subject.generateProvisioningKeyEndPoint(fogData, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('generateUuidResponse', () => provisionKey) - def('findIoFogResponse', () => Promise.resolve({ uuid: fogData.uuid, userId: user.id })) - def('updateOrCreateProvisionKeyResponse', () => Promise.resolve(newProvision)) - - def('dateResponse', () => date) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'generateUUID').returns($generateUuidResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - $sandbox.stub(ioFogProvisionKeyManager, 'updateOrCreate').returns($updateOrCreateProvisionKeyResponse) - - $sandbox.stub(Date.prototype, 'getTime').returns($dateResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(AppHelper, 'generateUUID').returns(provisionKey) + $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid }) + $sandbox.stub(ioFogProvisionKeyManager, 'updateOrCreate').callsFake((_where, payload) => Promise.resolve({ + provisionKey: payload.provisionKey, + expirationTime: payload.expirationTime + })) + $sandbox.stub(Date.prototype, 'getTime').returns(155555555) }) - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(fogData, Validator.schemas.iofogGenerateProvision) + it('creates a provisioning key', async () => { + const result = await $subject + expect(result).to.eql({ key: provisionKey, expirationTime, caCert: '' }) + expect(ioFogProvisionKeyManager.updateOrCreate).to.have.been.calledOnce }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when fog is missing', () => { + beforeEach(() => { + ioFogManager.findOne.resolves(null) }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#generateUUID() with correct args', async () => { - await $subject - - expect(AppHelper.generateUUID).to.have.been.called - }) - - context('when AppHelper#generateUUID() fails', () => { - def('generateUuidResponse', () => { throw error }) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('key') - }) - }) - - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith(queryFogData, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls ioFogProvisionKeyManager#updateOrCreate() with correct args', async () => { - await $subject - expect(ioFogProvisionKeyManager.updateOrCreate).to.have.been.calledWith({ - iofogUuid: fogData.uuid, - }, newProvision, transaction) - }) - - context('when ioFogProvisionKeyManager#updateOrCreate() fails', () => { - def('updateOrCreateProvisionKeyResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogProvisionKeyManager#updateOrCreate() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal({ - key: provisionKey, - expirationTime: expirationTime, - }) - }) - }) - }) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) describe('.setFogVersionCommandEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const uuid = 'testUuid' - - const date = 155555555 - - const fogVersionData = { - uuid: uuid, - versionCommand: 'upgrade', - } - - const queryFogData = { uuid: fogVersionData.uuid } - - const ioFog = { - uuid: uuid, - isReadyToUpgrade: true, - isReadyToRollback: true, - userId: user.id - } - - const newVersionCommand = { - iofogUuid: fogVersionData.uuid, - versionCommand: fogVersionData.versionCommand, - } - - const provisionKey = 'tttttttt' - const expirationTime = date + (20 * 60 * 1000) - - const newProvision = { - iofogUuid: uuid, - provisionKey: provisionKey, - expirationTime: expirationTime, - } + const fogVersionData = { uuid, versionCommand: 'upgrade' } def('subject', () => $subject.setFogVersionCommandEndPoint(fogVersionData, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findIoFogResponse', () => Promise.resolve(ioFog)) - def('generateUuidResponse', () => provisionKey) - def('updateOrCreateProvisionKeyResponse', () => Promise.resolve(newProvision)) - def('findIoFogVersionCommandResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse', () => Promise.resolve()) - - def('dateResponse', () => date) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - $sandbox.stub(AppHelper, 'generateUUID').returns($generateUuidResponse) - $sandbox.stub(ioFogProvisionKeyManager, 'updateOrCreate').returns($updateOrCreateProvisionKeyResponse) - $sandbox.stub(ioFogVersionCommandManager, 'updateOrCreate').returns($findIoFogVersionCommandResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - - $sandbox.stub(Date.prototype, 'getTime').returns($dateResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findOne').resolves({ + uuid, + isReadyToUpgrade: true, + isReadyToRollback: true + }) + $sandbox.stub(AppHelper, 'generateUUID').returns('provision-key') + $sandbox.stub(ioFogProvisionKeyManager, 'updateOrCreate').resolves({ + provisionKey: 'provision-key', + expirationTime: 155555555 + }) + $sandbox.stub(Date.prototype, 'getTime').returns(155555555) + $sandbox.stub(ioFogVersionCommandManager, 'updateOrCreate').resolves() + $sandbox.stub(ChangeTrackingService, 'update').resolves() }) - it('calls Validator#validate() with correct args', async () => { + it('stores version command after provisioning key refresh', async () => { await $subject - expect(Validator.validate).to.have.been.calledWith(fogVersionData, Validator.schemas.iofogSetVersionCommand) + expect(ioFogProvisionKeyManager.updateOrCreate).to.have.been.calledOnce + expect(ioFogVersionCommandManager.updateOrCreate).to.have.been.calledWith( + { iofogUuid: uuid }, + { iofogUuid: uuid, versionCommand: 'upgrade' }, + transaction + ) + expect(ChangeTrackingService.update).to.have.been.calledWith(uuid, ChangeTrackingService.events.version, transaction) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when upgrade is not allowed', () => { + beforeEach(() => { + ioFogManager.findOne.resolves({ uuid, isReadyToUpgrade: false, isReadyToRollback: true }) }) - }) - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith(queryFogData, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith({ - uuid: fogVersionData.uuid, - }, Validator.schemas.iofogGenerateProvision) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - - expect(AppHelper.generateRandomString).to.have.been.calledWith(8) - }) - - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith(queryFogData, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls ioFogProvisionKeyManager#updateOrCreate() with correct args', async () => { - await $subject - expect(ioFogProvisionKeyManager.updateOrCreate).to.have.been.calledWith({ - iofogUuid: uuid, - }, newProvision, transaction) - }) - - context('when ioFogProvisionKeyManager#updateOrCreate() fails', () => { - def('updateOrCreateProvisionKeyResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogProvisionKeyManager#updateOrCreate() succeeds', () => { - it('calls ioFogVersionCommandManager#updateOrCreate() with correct args', async () => { - await $subject - expect(ioFogVersionCommandManager.updateOrCreate).to.have.been.calledWith({ - iofogUuid: fogVersionData.uuid, - }, newVersionCommand, transaction) - }) - - context('when ioFogVersionCommandManager#updateOrCreate() fails', () => { - def('updateOrCreateProvisionKeyResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogVersionCommandManager#updateOrCreate() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(fogVersionData.uuid, - ChangeTrackingService.events.version, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateOrCreateProvisionKeyResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) - }) - }) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) }) }) describe('.setFogRebootCommandEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const uuid = 'testUuid' - - const date = 155555555 - - const fogData = { - uuid: uuid, - } - - const queryFogData = { uuid: fogData.uuid } + const fogData = { uuid } def('subject', () => $subject.setFogRebootCommandEndPoint(fogData, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findIoFogResponse', () => Promise.resolve({ uuid: fogData.uuid, userId: user.id })) - def('updateChangeTrackingResponse', () => Promise.resolve()) - - def('dateResponse', () => date) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid }) + $sandbox.stub(ChangeTrackingService, 'update').resolves() }) - it('calls Validator#validate() with correct args', async () => { + it('queues reboot change tracking', async () => { await $subject - expect(Validator.validate).to.have.been.calledWith(fogData, Validator.schemas.iofogReboot) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith(queryFogData, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(fogData.uuid, - ChangeTrackingService.events.reboot, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) + expect(ChangeTrackingService.update).to.have.been.calledWith(uuid, ChangeTrackingService.events.reboot, transaction) }) }) describe('.getHalHardwareInfoEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const uuid = 'testUuid' - - const date = 155555555 - - const uuidObj = { - uuid: uuid, - } + const uuidObj = { uuid: 'testUuid' } + const hwInfo = { cpu: 'arm64' } def('subject', () => $subject.getHalHardwareInfoEndPoint(uuidObj, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findIoFogResponse', () => Promise.resolve({ userId: user.id, uuid: uuidObj.uuid })) - def('findHalHardwareResponse', () => Promise.resolve()) - - def('dateResponse', () => date) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - $sandbox.stub(HWInfoManager, 'findOne').returns($findHalHardwareResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid: uuidObj.uuid }) + $sandbox.stub(HWInfoManager, 'findOne').resolves(hwInfo) }) - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(uuidObj, Validator.schemas.halGet) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith({ - uuid: uuidObj.uuid, - }, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls HWInfoManager#findOne() with correct args', async () => { - await $subject - expect(HWInfoManager.findOne).to.have.been.calledWith({ - iofogUuid: uuidObj.uuid, - }, transaction) - }) - - context('when HWInfoManager#findOne() fails', () => { - def('findHalHardwareResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when HWInfoManager#findOne() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) + it('returns HAL hardware info', async () => { + const result = await $subject + expect(result).to.equal(hwInfo) + expect(HWInfoManager.findOne).to.have.been.calledWith({ iofogUuid: uuidObj.uuid }, transaction) }) }) describe('.getHalUsbInfoEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const uuid = 'testUuid' - - const date = 155555555 - - const uuidObj = { - uuid: uuid, - } + const uuidObj = { uuid: 'testUuid' } + const usbInfo = { devices: [] } def('subject', () => $subject.getHalUsbInfoEndPoint(uuidObj, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findIoFogResponse', () => Promise.resolve({ userId: user.id, uuid: uuidObj.uuid })) - def('findHalUsbResponse', () => Promise.resolve()) - - def('dateResponse', () => date) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - $sandbox.stub(USBInfoManager, 'findOne').returns($findHalUsbResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(uuidObj, Validator.schemas.halGet) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid: uuidObj.uuid }) + $sandbox.stub(USBInfoManager, 'findOne').resolves(usbInfo) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith({ - uuid: uuidObj.uuid, - }, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls USBInfoManager#findOne() with correct args', async () => { - await $subject - expect(USBInfoManager.findOne).to.have.been.calledWith({ - iofogUuid: uuidObj.uuid, - }, transaction) - }) - - context('when USBInfoManager#findOne() fails', () => { - def('findHalUsbResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when USBInfoManager#findOne() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) + it('returns HAL USB info', async () => { + const result = await $subject + expect(result).to.equal(usbInfo) + expect(USBInfoManager.findOne).to.have.been.calledWith({ iofogUuid: uuidObj.uuid }, transaction) }) }) }) diff --git a/test/src/services/microservices-service.test.js b/test/src/services/microservices-service.test.js index cbb01bdb..7ae8eb72 100644 --- a/test/src/services/microservices-service.test.js +++ b/test/src/services/microservices-service.test.js @@ -6,2242 +6,394 @@ const MicroservicesService = require('../../../src/services/microservices-servic const AppHelper = require('../../../src/helpers/app-helper') const Validator = require('../../../src/schemas') const ChangeTrackingService = require('../../../src/services/change-tracking-service') -const CatalogService = require('../../../src/services/catalog-service') -const ApplicationService = require('../../../src/services/application-service') const ApplicationManager = require('../../../src/data/managers/application-manager') -const MicroservicePortManager = require('../../../src/data/managers/microservice-port-manager') +const MicroservicePortService = require('../../../src/services/microservice-ports/microservice-port') const CatalogItemImageManager = require('../../../src/data/managers/catalog-item-image-manager') -const RouterManager = require('../../../src/data/managers/router-manager') -const VolumeMappingManager = require('../../../src/data/managers/volume-mapping-manager') const MicroserviceStatusManager = require('../../../src/data/managers/microservice-status-manager') +const MicroserviceExecStatusManager = require('../../../src/data/managers/microservice-exec-status-manager') +const RegistryManager = require('../../../src/data/managers/registry-manager') +const FogManager = require('../../../src/data/managers/iofog-manager') +const ServiceManager = require('../../../src/data/managers/service-manager') +const VolumeMappingManager = require('../../../src/data/managers/volume-mapping-manager') const MicroserviceExtraHostManager = require('../../../src/data/managers/microservice-extra-host-manager') const MicroserviceEnvManager = require('../../../src/data/managers/microservice-env-manager') const MicroserviceArgManager = require('../../../src/data/managers/microservice-arg-manager') -const RegistryManager = require('../../../src/data/managers/registry-manager') -const Op = require('sequelize').Op -const MicroservicePublicPortManager = { - findAll: () => Promise.resolve([]), - create: () => Promise.resolve(), - updateOrCreate: () => Promise.resolve() -} -const ioFogManager = require('../../../src/data/managers/iofog-manager') -const ioFogService = require('../../../src/services/iofog-service') +const MicroserviceCdiDevManager = require('../../../src/data/managers/microservice-cdi-device-manager') +const MicroserviceCapAddManager = require('../../../src/data/managers/microservice-cap-add-manager') +const MicroserviceCapDropManager = require('../../../src/data/managers/microservice-cap-drop-manager') +const MicroserviceHealthCheckManager = require('../../../src/data/managers/microservice-healthcheck-manager') +const RbacRoleManager = require('../../../src/data/managers/rbac-role-manager') +const RbacServiceAccountManager = require('../../../src/data/managers/rbac-service-account-manager') +const NatsAuthService = require('../../../src/services/nats-auth-service') const Errors = require('../../../src/helpers/errors') -const Constants = require('../../../src/helpers/constants') -describe('Microservices Service', () => { - def('subject', () => MicroservicesService) - def('sandbox', () => sinon.createSandbox()) - - const isCLI = true +const transaction = {} +const isCLI = true + +function buildMicroserviceRecord (fields = {}) { + return { + uuid: 'msvc-uuid', + name: 'test-msvc', + applicationId: 42, + iofogUuid: 'fog-uuid', + registryId: 1, + delete: false, + catalogItem: null, + isController: false, + logSize: 1024, + getPorts: () => Promise.resolve([]), + ...fields + } +} - afterEach(() => $sandbox.restore()) +function stubBuildGetResponseDeps (sandbox) { + sandbox.stub(MicroservicePortService, 'getPortMappings').resolves([]) + sandbox.stub(MicroserviceExtraHostManager, 'findAll').resolves([]) + sandbox.stub(CatalogItemImageManager, 'findAll').resolves([]) + sandbox.stub(VolumeMappingManager, 'findAll').resolves([]) + sandbox.stub(MicroserviceEnvManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceArgManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceCdiDevManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceCapAddManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceCapDropManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceStatusManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceExecStatusManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceHealthCheckManager, 'findAllExcludeFields').resolves([]) +} - describe('.listMicroservicesEndPoint()', () => { - const transaction = {} - const error = 'Error!' +function stubServiceAccountDeps (sandbox) { + sandbox.stub(VolumeMappingManager, 'findOne').resolves(null) + sandbox.stub(VolumeMappingManager, 'create').resolves({ uuid: 'vol-uuid' }) + sandbox.stub(RbacRoleManager, 'getRoleWithRules').resolves({ name: 'microservice' }) + sandbox.stub(RbacServiceAccountManager, 'findOneByMicroserviceUuid').resolves(null) + sandbox.stub(RbacServiceAccountManager, 'createServiceAccount').resolves({ id: 1 }) +} - const user = { - id: 15, - } +function stubCreateMicroserviceDeps (sandbox, { msvcUuid = 'msvc-uuid', appId = 42, fogUuid = 'fog-uuid' } = {}) { + const fog = { uuid: fogUuid, archId: 1, name: 'edge-1', availableRuntimes: [] } + const application = { id: appId, name: 'my-app', isSystem: false, natsAccess: false } + const created = buildMicroserviceRecord({ + uuid: msvcUuid, + name: 'new-msvc', + applicationId: appId, + iofogUuid: fogUuid + }) - const application = { - name: 'my-app', - id: 42 + sandbox.stub(Validator, 'validate').resolves(true) + sandbox.stub(FogManager, 'findOne').resolves(fog) + sandbox.stub(ApplicationManager, 'findOne').resolves(application) + sandbox.stub(RegistryManager, 'findOne').resolves({ id: 1 }) + sandbox.stub(AppHelper, 'generateUUID').returns(msvcUuid) + sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + sandbox.stub(MicroserviceManager, 'create').resolves(created) + sandbox.stub(MicroserviceManager, 'findOne').callsFake((where) => { + if (where && where.name) { + return Promise.resolve(null) } - - const microserviceStatus = { - 'containerId': 'testContainerId', - 'status': 'RUNNING', - 'startTime': 5325543453454, - 'operatingDuration': 534535435435, - 'cpuUsage': 35, - 'memoryUsage': 45, - 'percentage': 50.5, - 'errorMessage': '' + if (where && where.uuid) { + return Promise.resolve({ uuid: msvcUuid, applicationId: appId }) } + return Promise.resolve(null) + }) + sandbox.stub(MicroservicePortService, 'validatePortMappings').resolves() + sandbox.stub(CatalogItemImageManager, 'bulkCreate').resolves() + sandbox.stub(MicroserviceStatusManager, 'create').resolves() + sandbox.stub(MicroserviceExecStatusManager, 'create').resolves() + sandbox.stub(ChangeTrackingService, 'update').resolves() + stubServiceAccountDeps(sandbox) +} - const uuid = 'testUuid' +function stubUpdateMicroserviceDeps (sandbox, existing) { + const fog = { uuid: existing.iofogUuid, archId: 1, name: 'edge-1', availableRuntimes: [] } + + sandbox.stub(MicroserviceManager, 'findOne').resolves(existing) + sandbox.stub(Validator, 'validate').resolves(true) + sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + sandbox.stub(FogManager, 'findOne').resolves(fog) + sandbox.stub(MicroserviceManager, 'findOneWithCategory').resolves({ + ...existing, + catalogItem: existing.catalogItem || null, + getPorts: () => Promise.resolve([]), + getImages: () => Promise.resolve([]) + }) + sandbox.stub(CatalogItemImageManager, 'findAll').resolves([]) + sandbox.stub(ApplicationManager, 'findOne').resolves({ id: existing.applicationId, natsAccess: false }) + sandbox.stub(MicroserviceManager, 'updateAndFind').resolves(existing) + sandbox.stub(MicroserviceExtraHostManager, 'findAll').resolves([]) + sandbox.stub(ServiceManager, 'findOne').resolves(null) + sandbox.stub(ChangeTrackingService, 'update').resolves() + stubServiceAccountDeps(sandbox) +} - const response = [ - { - uuid, - applicationId: application.id, - // dataValues is being directly accessed on top of sequelize getters - dataValues: { - uuid, - applicationId: application.id, - } - }, - ] +describe('Microservices Service', () => { + def('service', () => MicroservicesService) + def('sandbox', () => sinon.createSandbox()) - + afterEach(() => $sandbox.restore()) - const responseObject = [{ - cmd: [], - env: [], - extraHosts: [], - images: [], - logSize: NaN, - ports: [], - volumeMappings: [], - flowId: application.id, - applicationId: application.id, - application: application.name, - status: microserviceStatus, - uuid - }] - const microserviceResponse = { - microservices: responseObject + describe('.listMicroservicesEndPoint()', () => { + const application = { id: 42, name: 'my-app', isSystem: false } + const microserviceRow = { + dataValues: buildMicroserviceRecord(), + uuid: 'msvc-uuid', + applicationId: 42 } - const microserviceStatusArray = [microserviceStatus] - def('subject', () => $subject.listMicroservicesEndPoint({ applicationName: application.name }, user, isCLI, transaction)) - def('findMicroservicesResponse', () => Promise.resolve(response)) - def('findPortMappingsResponse', () => Promise.resolve([])) - def('findVolumeMappingsResponse', () => Promise.resolve([])) - def('findExtraHostsResponse', () => Promise.resolve([])) - def('publicModeResponse', () => Promise.resolve([])) - def('envResponse', () => Promise.resolve([])) - def('cmdResponse', () => Promise.resolve([])) - def('imgResponse', () => Promise.resolve([])) - def('statusResponse', () => microserviceStatusArray) + def('subject', () => $service.listMicroservicesEndPoint({ applicationName: 'my-app' }, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findAllExcludeFields').returns($findMicroservicesResponse) - $sandbox.stub(MicroservicePortManager, 'findAll').returns($findPortMappingsResponse) - $sandbox.stub(VolumeMappingManager, 'findAll').returns($findVolumeMappingsResponse) - $sandbox.stub(MicroserviceExtraHostManager, 'findAll').returns($findExtraHostsResponse) - $sandbox.stub(MicroserviceEnvManager, 'findAllExcludeFields').returns($envResponse) - $sandbox.stub(MicroserviceArgManager, 'findAllExcludeFields').returns($cmdResponse) - $sandbox.stub(CatalogItemImageManager, 'findAll').returns($imgResponse) - $sandbox.stub(MicroserviceStatusManager, 'findAllExcludeFields').returns($statusResponse) - $sandbox.stub(ApplicationManager, 'findOne').returns(Promise.resolve(application)) - }) - - it('calls MicroserviceManager#findAllExcludeFields() with correct args', async () => { - await $subject - const where = application ? { applicationId: application.id, delete: false } : { delete: false } - if (!isCLI) { - where.userId = user.id - } - - expect(MicroserviceManager.findAllExcludeFields).to.have.been.calledWith(where, transaction) + stubBuildGetResponseDeps($sandbox) + $sandbox.stub(ApplicationManager, 'findOne').resolves(application) + $sandbox.stub(MicroserviceManager, 'findAllExcludeFields').resolves([microserviceRow]) }) - context('when MicroserviceManager#findAllExcludeFields() fails', () => { - def('findMicroservicesResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('lists microservices for an application', async () => { + const result = await $subject + expect(MicroserviceManager.findAllExcludeFields).to.have.been.calledWith( + { applicationId: 42, delete: false }, + transaction + ) + expect(result.microservices).to.have.length(1) + expect(result.microservices[0].uuid).to.equal('msvc-uuid') }) - context('when MicroserviceManager#findAllExcludeFields() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('microservices') + context('when application is missing', () => { + beforeEach(() => { + ApplicationManager.findOne.rejects(new Errors.NotFoundError('app missing')) }) - }) - context('when MicroserviceManager#findAllExcludeFields() succeeds and return microservices', () => { - it('fulfills the promise', async() => { - await $subject - return expect($subject).to.eventually.deep.equal(microserviceResponse) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) describe('.getMicroserviceEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const application = { - name: 'my-app', - id: 42 + const msvcUuid = 'msvc-uuid' + const microserviceRow = { + dataValues: buildMicroserviceRecord({ uuid: msvcUuid, logSize: 1024 }), + uuid: msvcUuid } - const microserviceUuid = 'testMicroserviceUuid' - - const response = { - dataValues: { - uuid: 'testUuid', - }, - } - - def('subject', () => $subject.getMicroserviceEndPoint(microserviceUuid, user, isCLI, transaction)) - def('findMicroserviceResponse', () => Promise.resolve(response)) - def('findPortMappingsResponse', () => Promise.resolve([])) - def('findVolumeMappingsResponse', () => Promise.resolve([])) - def('findExtraHostsResponse', () => Promise.resolve([])) - def('publicModeResponse', () => Promise.resolve([])) - def('envResponse', () => Promise.resolve([])) - def('cmdResponse', () => Promise.resolve([])) - def('imgResponse', () => Promise.resolve([])) - def('statusResponse', () => Promise.resolve([])) + def('subject', () => $service.getMicroserviceEndPoint(msvcUuid, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findOneExcludeFields').returns($findMicroserviceResponse) - $sandbox.stub(MicroservicePortManager, 'findAll').returns($findPortMappingsResponse) - $sandbox.stub(VolumeMappingManager, 'findAll').returns($findVolumeMappingsResponse) - $sandbox.stub(MicroserviceExtraHostManager, 'findAll').returns($findExtraHostsResponse) - $sandbox.stub(MicroserviceEnvManager, 'findAllExcludeFields').returns($envResponse) - $sandbox.stub(MicroserviceArgManager, 'findAllExcludeFields').returns($cmdResponse) - $sandbox.stub(CatalogItemImageManager, 'findAll').returns($imgResponse) - $sandbox.stub(MicroserviceStatusManager, 'findAllExcludeFields').returns($statusResponse) - $sandbox.stub(ApplicationManager, 'findOne').returns(Promise.resolve(application)) + stubBuildGetResponseDeps($sandbox) + $sandbox.stub(ApplicationManager, 'findOne').resolves({ name: 'my-app' }) + $sandbox.stub(MicroserviceManager, 'findOneExcludeFields').resolves(microserviceRow) }) - it('calls MicroserviceManager#findOneExcludeFields() with correct args', async () => { - await $subject - expect(MicroserviceManager.findOneExcludeFields).to.have.been.calledWith({ - uuid: microserviceUuid, delete: false, - }, transaction) - }) - - context('when MicroserviceManager#findOneExcludeFields() fails', () => { - def('findMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('returns enriched microservice data', async () => { + const result = await $subject + expect(MicroserviceManager.findOneExcludeFields).to.have.been.calledWith( + { uuid: msvcUuid, delete: false }, + transaction + ) + expect(result.uuid).to.equal(msvcUuid) }) - context('when MicroserviceManager#findOneExcludeFields() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('uuid') + context('when microservice is missing', () => { + beforeEach(() => { + MicroserviceManager.findOneExcludeFields.resolves(null) }) - }) - - context('when microservice has extraHosts', () => { - const extraHosts = [{ - id: 1, - targetFogUuid: 'tfog', - targetMicroserviceUuid: 'tmicroservice', - template: '${Apps.application.msvc.local}', - value: '1.2.3.4', - name: 'testExtraHost' - }] - def('findExtraHostsResponse', () => Promise.resolve(extraHosts)) - it('returns extra hosts', async () => { - const ms = await $subject - expect(ms).to.have.property('extraHosts') - expect(ms.extraHosts).to.have.length(extraHosts.length) - expect(ms.extraHosts).to.eql(extraHosts.map(e => ({value: e.value, name: e.name, address: e.template}))) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) - context('when microservice has public ports', () => { - def('findPortMappingsResponse', () => Promise.resolve([ - { - id: 1, - portInternal: 80, - portInternal: 8080, - isPublic: true, - getPublicPort: () => Promise.resolve({ - hostId: 'fakeAgentUuid', - publicPort: 1234, - protocol: 'http', - schemes: JSON.stringify(['http']) - }) - }, - ])) + context('when called from REST (non-CLI)', () => { + def('subject', () => $service.getMicroserviceEndPoint(msvcUuid, false, transaction)) beforeEach(() => { - $sandbox.stub(RouterManager, 'findOne').returns(Promise.resolve({host: '1.2.3.4'})) - $sandbox.stub(ioFogManager, 'findOne').returns(Promise.resolve({})) + $sandbox.stub(MicroserviceManager, 'findMicroserviceOnGet').resolves(microserviceRow) }) - it('returns public links', async () => { - const ms = await $subject - expect(ms).to.have.property('ports') - expect(ms.ports).to.have.length(1) - expect(ms.ports[0]).to.have.property('public') - expect(ms.ports[0].public).to.have.property('links') - expect(ms.ports[0].public.links).to.deep.equal(['http://1.2.3.4:1234']) + it('validates user access before read', async () => { + await $subject + expect(MicroserviceManager.findMicroserviceOnGet).to.have.been.calledWith({ uuid: msvcUuid }, transaction) }) }) }) describe('.createMicroserviceEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const application = { - name: 'my-app', - active: true, - id: 42 - } - + const msvcUuid = 'new-msvc-uuid' const microserviceData = { - 'name': 'name2', - 'config': 'string', - 'catalogItemId': 15, - 'application': application.name, - 'iofogUuid': 'testIofogUuid', - 'rootHostAccess': true, - 'logSize': 1, - 'volumeMappings': [ - { - 'hostDestination': '/var/dest', - 'containerDestination': '/var/dest', - 'accessMode': 'rw', - 'type': 'bind' - }, - ], - 'ports': [ - { - 'internal': 1, - 'external': 1, - }, - ], - 'logLimit': 1 - } - - const newMicroserviceUuid = 'newMicroserviceUuid' - const newMicroservice = { - uuid: newMicroserviceUuid, - name: microserviceData.name, - config: microserviceData.config, - catalogItemId: microserviceData.catalogItemId, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: microserviceData.rootHostAccess, - logSize: microserviceData.logSize, - registryId: 1, - userId: user.id, - } - - const fog = { - uuid: microserviceData.iofogUuid, - archId: 1, - name: 'testfog' - } - - const item = {} - - const portMappingData = - { - 'internal': 1, - 'external': 1, - } - - const mappings = [] - for (const volumeMapping of microserviceData.volumeMappings) { - const mapping = Object.assign({}, volumeMapping) - mapping.microserviceUuid = newMicroservice.uuid - mappings.push(mapping) - } - - const mappingData = { - isProxy: false, - isPublic: false, - isUdp: false, - portInternal: portMappingData.internal, - portExternal: portMappingData.external, - userId: newMicroservice.userId, - microserviceUuid: newMicroservice.uuid, + name: 'new-msvc', + application: 'my-app', + iofogUuid: 'fog-uuid', + images: [{ containerImage: 'demo:latest', archId: 1 }], + registryId: 1 } - const images = [ - {archId: 1, containerImage: 'hello-world'}, - {archId: 2, containerImage: 'hello-world'}, - ] - - const proxyCatalogItem = { - id: 42 - } - - const systemFog = { - isSystem: true, - uuid: 'systemFogUuid', - getMicroservice: () => Promise.resolve([]) - } - - const defaultRouter = { - id: 21 - } - - const extraHostFog = { - uuid: 'extraHostFogUuid', - host: 'extraHostFogHost' - } - - const extraHostFogPublic = { - uuid: 'extraHostFogPublicUuid', - host: 'extraHostFogPublicHost' - } - - const extraHostMsvc = { - uuid: 'extraHostUuid', - iofogUuid: extraHostFog.uuid, - } - - def('subject', () => $subject.createMicroserviceEndPoint(microserviceData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('validatorResponse2', () => Promise.resolve(true)) - def('generateRandomStringResponse', () => newMicroserviceUuid) - def('deleteUndefinedFieldsResponse', () => ({...newMicroservice})) - def('findMicroserviceResponse', () => Promise.resolve()) - def('findMicroserviceResponse2', () => Promise.resolve(microserviceData)) - def('getCatalogItemResponse', () => Promise.resolve({images})) - def('findApplicationResponse', () => Promise.resolve(application)) - def('getIoFogResponse', () => Promise.resolve()) - def('createMicroserviceResponse', () => Promise.resolve({...newMicroservice})) - def('findMicroservicePortResponse', () => Promise.resolve()) - def('createMicroservicePortResponse', () => Promise.resolve({id: 15})) - def('updateMicroserviceResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse', () => Promise.resolve()) - def('createVolumeMappingResponse', () => Promise.resolve()) - def('createMicroserviceStatusResponse', () => Promise.resolve()) - def('findOneRegistryResponse', () => Promise.resolve({})) - def('findOneFogResponse', () => Promise.resolve({...fog, getMicroservice: () => Promise.resolve([])})) - def('findPublicPortsResponse', () => Promise.resolve([])) - def('getProxyCatalogItem', () => Promise.resolve((proxyCatalogItem))) - def('proxyCatalogItem', () => Promise.resolve(null)) - def('findDefaultFogResponse', () => Promise.resolve(systemFog)) - def('findDefaultRouterResponse', () => Promise.resolve(defaultRouter)) - def('getProxyMsvcResponse', () => Promise.resolve(null)) - def('getExtraHostMsvc', () => Promise.resolve(extraHostMsvc)) - def('getExtraHostFog', () => Promise.resolve(extraHostFog)) - def('getExtraHostFogPublic', () => Promise.resolve(extraHostFogPublic)) + def('subject', () => $service.createMicroserviceEndPoint(microserviceData, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate') - .onFirstCall().returns($validatorResponse) - .onSecondCall().returns($validatorResponse2) - $sandbox.stub(AppHelper, 'generateRandomString').returns($generateRandomStringResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(MicroserviceManager, 'findOne') - .onFirstCall().returns($findMicroserviceResponse) - .onSecondCall().returns($findMicroserviceResponse2) - .withArgs({ catalogItemId: proxyCatalogItem.id, iofogUuid: microserviceData.iofogUuid }).returns($getProxyMsvcResponse) // find proxy microservice in public port - .withArgs({ applicationId: application.id, name: 'msvc' }).returns($getExtraHostMsvc) // find extraHostMsvc target in extra host - $sandbox.stub(CatalogService, 'getCatalogItem').returns($getCatalogItemResponse) - $sandbox.stub(ApplicationManager, 'findOne').returns($findApplicationResponse) - $sandbox.stub(CatalogService, 'getProxyCatalogItem').returns($getProxyCatalogItem) - $sandbox.stub(MicroserviceManager, 'create').returns($createMicroserviceResponse) - $sandbox.stub(MicroservicePortManager, 'findOne').returns($findMicroservicePortResponse) - $sandbox.stub(MicroservicePortManager, 'create').returns($createMicroservicePortResponse) - $sandbox.stub(MicroserviceManager, 'update').returns($updateMicroserviceResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - $sandbox.stub(VolumeMappingManager, 'bulkCreate').returns($createVolumeMappingResponse) - $sandbox.stub(MicroserviceStatusManager, 'create').returns($createMicroserviceStatusResponse) - $sandbox.stub(RegistryManager, 'findOne').returns($findOneRegistryResponse) - const stub = $sandbox.stub(ioFogManager, 'findOne') - stub.withArgs({isSystem: true}).returns($findDefaultFogResponse) - stub.withArgs({uuid: extraHostMsvc.iofogUuid}).returns($getExtraHostFog) - stub.withArgs({uuid: extraHostFogPublic.uuid}).returns($getExtraHostFogPublic) - stub.returns($findOneFogResponse) - $sandbox.stub(MicroservicePublicPortManager, 'findAll').returns($findPublicPortsResponse) + stubCreateMicroserviceDeps($sandbox, { msvcUuid }) }) - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(microserviceData, - Validator.schemas.microserviceCreate) + it('validates input and creates a microservice', async () => { + const result = await $subject + expect(Validator.validate).to.have.been.calledWith(microserviceData, Validator.schemas.microserviceCreate) + expect(MicroserviceManager.create).to.have.been.calledOnce + expect(RbacServiceAccountManager.createServiceAccount).to.have.been.calledOnce + expect(result).to.eql({ uuid: msvcUuid, name: 'new-msvc' }) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('rejects top-level natsAccess (use natsConfig)', () => { + const badPayload = { ...microserviceData, natsAccess: true } + return expect( + $service.createMicroserviceEndPoint(badPayload, isCLI, transaction) + ).to.be.rejectedWith('natsAccess must be provided under natsConfig.natsAccess') }) - - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - expect(AppHelper.generateRandomString).to.have.been.calledWith(32) - }) - - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse', () => error) - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('uuid') - }) + context('when fog is missing', () => { + beforeEach(() => { + FogManager.findOne.resolves(null) }) - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(newMicroservice) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - const err = 'Invalid microservice UUID \'undefined\'' - def('deleteUndefinedFieldsResponse', () => Promise.reject(err)) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls MicroserviceManager#findOne() with correct args', async () => { - await $subject - const where = item.id - ? - { - name: microserviceData.name, - uuid: { [Op.ne]: item.id }, - userId: user.id, - applicationId: application.id, - delete: false - } - : - { - name: microserviceData.name, - userId: user.id, - applicationId: application.id, - delete: false - } - expect(MicroserviceManager.findOne).to.have.been.calledWith(where, transaction) - }) - - context('when MicroserviceManager#findOne() fails', () => { - def('findMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#findOne() succeeds', () => { - it('calls CatalogService#getCatalogItem() with correct args', async () => { - await $subject - expect(CatalogService.getCatalogItem).to.have.been.calledWith(newMicroservice.catalogItemId, - user, isCLI, transaction) - }) - - context('when CatalogService#getCatalogItem() fails', () => { - def('getCatalogItemResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogService#getCatalogItem() succeeds', () => { - it('calls ApplicationManager#findOne() with correct args', async () => { - await $subject - expect(ApplicationManager.findOne).to.have.been.calledWith({name: microserviceData.application}, transaction) - }) - - context('when ApplicationManager#findOne() fails', () => { - def('findApplicationResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ApplicationManager#findOne() returns null', () => { - def('findApplicationResponse', () => Promise.resolve(null)) - - it(`fails with ${error}`, async () => { - try { - await $subject - } catch (e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - return expect(true).to.eql(false) - }) - }) - - context('when ApplicationManager#findOne() succeeds', () => { - it('calls FogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith({ - uuid: newMicroservice.iofogUuid, - }, transaction) - }) - - context('when FogManager#findOne() fails', () => { - def('findOneFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when FogManager#findOne() succeeds', () => { - it('calls MicroserviceManager#create() with correct args', async () => { - await $subject - expect(MicroserviceManager.create).to.have.been.calledWith({...newMicroservice, applicationId: application.id}, - transaction) - }) - - - context('when there is no valid image in the catalog', () => { - const catalogItemNoImages = { - id: 3, - images: [] - } - def('findCatalogItem', () => Promise.resolve(catalogItemNoImages)) - it('Should throw validation error', async () => { - try { - await $subject - } catch (e) { - expect(e).to.be.instanceOf(Errors.ValidationError) - } - }) - }) - - context('when there is no valid image in microservice', () => { - def('subject', () => MicroservicesService.createMicroserviceEndPoint({...microserviceData, images: []}, user, isCLI, transaction)) - it('Should throw validation error', async () => { - try { - await $subject - } catch (e) { - expect(e).to.be.instanceOf(Errors.ValidationError) - } - }) - }) - - context('when MicroserviceManager#create() fails', () => { - def('findOneFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#create() succeeds', () => { - it('calls MicroservicePortManager#findOne() with correct args', async () => { - await $subject - expect(MicroservicePortManager.findOne).to.have.been.calledWith({ - microserviceUuid: newMicroservice.uuid, - [Op.or]: - [ - { - portInternal: portMappingData.internal, - }, - { - portExternal: portMappingData.external, - }, - ], - }, transaction) - }) - context('when MicroservicePortManager#findOne() fails', () => { - def('findMicroservicePortResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroservicePortManager#findOne() succeeds', () => { - it('calls MicroservicePortManager#create() with correct args', async () => { - await $subject - expect(MicroservicePortManager.create).to.have.been.calledWith(mappingData, transaction) - }) - - context('when portMapping is public', () => { - def('findRelatedExtraHosts', () => Promise.resolve([])) - const router = { - host: 'routerHost', - messagingPort: 5672 - } - let routerStub - const public = { - schemes: ['http'] - } - beforeEach(() => { - microserviceData.ports[0].public = public - routerStub = $sandbox.stub(RouterManager, 'findOne').returns(Promise.resolve(router)) - $sandbox.stub(MicroserviceExtraHostManager, 'findAll').returns($findRelatedExtraHosts) - $sandbox.stub(MicroservicePublicPortManager, 'create') - }) - - afterEach(() => { - delete microserviceData.ports[0].public - }) - - it('should create local and remote proxy microservices', async () => { - await $subject - const networkRouter = { - host: router.host, - port: router.messagingPort - } - const localProxy = { - uuid: sinon.match.string, - name: 'Proxy', - config: JSON.stringify({ - mappings: [`amqp:${newMicroserviceUuid}=>http:${microserviceData.ports[0].external}`], - networkRouter: networkRouter - }), - catalogItemId: proxyCatalogItem.id, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: true, - registryId: 1, - userId: user.id - } - const remoteProxy = { - uuid: sinon.match.string, - name: 'Proxy', - config: JSON.stringify({ - mappings: [`http:${6000}=>amqp:${newMicroserviceUuid}`], - networkRouter: networkRouter - }), - catalogItemId: proxyCatalogItem.id, - iofogUuid: systemFog.uuid, - rootHostAccess: true, - registryId: 1, - userId: user.id - } - expect(MicroserviceManager.create).to.have.been.calledWith(remoteProxy, transaction) - expect(MicroserviceManager.create).to.have.been.calledWith(localProxy, transaction) - }) - - it('Should create the public port', async () => { - await $subject - const publicPortData = { - portId: 15, - hostId: systemFog.uuid, - localProxyId: newMicroservice.uuid, - remoteProxyId: newMicroservice.uuid, - publicPort: 6000, - queueName: newMicroserviceUuid, - isTcp: false, - schemes: JSON.stringify(['http']) - } - expect(MicroservicePublicPortManager.create).to.have.been.calledWith(publicPortData, transaction) - }) - - context('When there are related extra hosts', () => { - const extraHosts = [{ - save: () => {}, - publicPort: 6000, - microserviceUuid: newMicroserviceUuid - }] - def('findRelatedExtraHosts', () => Promise.resolve(extraHosts)) - beforeEach(() => { - $sandbox.stub(MicroserviceExtraHostManager, 'updateOriginMicroserviceChangeTracking') - }) - - it('Should update the extraHost with the host values', async () => { - await $subject - for (const e of extraHosts) { - expect(MicroserviceExtraHostManager.updateOriginMicroserviceChangeTracking).to.have.been.calledWith({...e, value: systemFog.host, targetFogUuid: systemFog.uuid}, transaction) - } - }) - }) - - context('when there is no system fog (K8s)', () => { - def('findDefaultFogResponse', () => Promise.resolve(null)) - - beforeEach(() => { - routerStub.withArgs({isSystem: true}).returns($findDefaultRouterResponse) - }) - it('should only create local proxy microservices', async () => { - await $subject - const networkRouter = { - host: router.host, - port: router.messagingPort - } - const localProxy = { - uuid: sinon.match.string, - name: 'Proxy', - config: JSON.stringify({ - mappings: [`amqp:${newMicroserviceUuid}=>http:${microserviceData.ports[0].external}`], - networkRouter: networkRouter - }), - catalogItemId: proxyCatalogItem.id, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: true, - registryId: 1, - userId: user.id - } - const remoteProxy = { - uuid: sinon.match.string, - name: 'Proxy', - config: JSON.stringify({ - mappings: [`http:${6000}=>amqp:${newMicroserviceUuid}`], - networkRouter: networkRouter - }), - catalogItemId: proxyCatalogItem.id, - iofogUuid: systemFog.uuid, - rootHostAccess: true, - registryId: 1, - userId: user.id - } - expect(MicroserviceManager.create).to.have.been.calledWith(localProxy, transaction) - expect(MicroserviceManager.create).not.to.have.been.calledWith(remoteProxy, transaction) - }) - - it('Should create the public port without remote host/proxy info', async () => { - await $subject - const publicPortData = { - portId: 15, - hostId: null, - localProxyId: newMicroservice.uuid, - remoteProxyId: null, - publicPort: 6000, - queueName: newMicroserviceUuid, - isTcp: false, - schemes: JSON.stringify(['http']) - } - expect(MicroservicePublicPortManager.create).to.have.been.calledWith(publicPortData, transaction) - }) - }) - - }) - - context('when MicroservicePortManager#create() fails', () => { - def('createMicroservicePortResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroservicePortManager#create() succeeds', () => { - it('calls MicroserviceManager#update() with correct args', async () => { - await $subject - const updateRebuildMs = { - rebuild: true, - } - expect(MicroserviceManager.update).to.have.been.calledWith({ - uuid: newMicroservice.uuid, - }, updateRebuildMs, transaction) - }) - - context('when MicroserviceManager#update() fails', () => { - def('updateMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#update() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(microserviceData.iofogUuid, - ChangeTrackingService.events.microserviceConfig, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('calls VolumeMappingManager#bulkCreate() with correct args', async () => { - await $subject - expect(VolumeMappingManager.bulkCreate).to.have.been.calledWith(mappings, - transaction) - }) - - context('when VolumeMappingManager#bulkCreate() fails', () => { - def('createVolumeMappingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when VolumeMappingManager#bulkCreate() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(microserviceData.iofogUuid, - ChangeTrackingService.events.microserviceList, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('calls MicroserviceStatusManager#create() with correct args', async () => { - await $subject - expect(MicroserviceStatusManager.create).to.have.been.calledWith({ - microserviceUuid: newMicroservice.uuid, - }, transaction) - }) - - context('when MicroserviceStatusManager#create() fails', () => { - def('createMicroserviceStatusResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceStatusManager#create() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) - - context('When there are extraHosts', () => { - const extraHosts = [{ - name: 'litteral', - address: 'litteral' - }, { - name: 'agent', - address: '${Agents.test}' - }, { - name: 'localMsvc', - address: '${Apps.application.msvc.local}' - }, { - name: 'publicMsvc', - address: '${Apps.application.msvc.public.5000}' - }] - beforeEach(() => { - microserviceData.extraHosts = extraHosts - $sandbox.stub(MicroserviceExtraHostManager, 'create') - $sandbox.stub(MicroservicePortManager, 'findAllPublicPorts').returns(Promise.resolve([{ - publicPort: { - publicPort: 5000, - hostId: extraHostFogPublic.uuid - } - }])) - }) - afterEach(() => { - delete microserviceData.extraHosts - }) - - it('Should create extra hosts', async () => { - await $subject - const defaultExtraHost = { - microserviceUuid: newMicroserviceUuid - } - expect(MicroserviceExtraHostManager.create).to.have.been.calledWith({ - name: 'litteral', - template: 'litteral', - templateType: 'Litteral', - value: 'litteral', - ...defaultExtraHost - }, transaction) - expect(MicroserviceExtraHostManager.create).to.have.been.calledWith({ - name: 'agent', - template: '${Agents.test}', - templateType: 'Agents', - value: fog.host, - targetFogUuid: fog.uuid, - ...defaultExtraHost - }, transaction) - expect(MicroserviceExtraHostManager.create).to.have.been.calledWith({ - name: 'localMsvc', - template: '${Apps.application.msvc.local}', - templateType: 'Apps', - value: extraHostFog.host, - targetFogUuid: extraHostFog.uuid, - targetMicroserviceUuid: extraHostMsvc.uuid, - ...defaultExtraHost - }, transaction) - expect(MicroserviceExtraHostManager.create).to.have.been.calledWith({ - name: 'publicMsvc', - template: '${Apps.application.msvc.public.5000}', - templateType: 'Apps', - value: extraHostFogPublic.host, - targetFogUuid: extraHostFogPublic.uuid, - targetMicroserviceUuid: extraHostMsvc.uuid, - publicPort: 5000, - ...defaultExtraHost - }, transaction) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) }) }) describe('.updateMicroserviceEndPoint()', () => { - const transaction = {}; - const error = 'Error!'; - - const user = { - id: 15 - }; - - const microserviceUuid = 'testMicroserviceUuid'; - - const query = isCLI - ? - { - uuid: microserviceUuid - } - : - { - uuid: microserviceUuid, - userId: user.id - }; - - const microserviceData = { - "name": "name2", - "config": "string", - "catalogItemId": 15, - "applicationId": 16, - "iofogUuid": 'testIofogUuid', - "rootHostAccess": true, - "logSize": 1, - }; - - const microserviceToUpdate = { - name: microserviceData.name, - config: microserviceData.config, - rebuild: microserviceData.rebuild, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: microserviceData.rootHostAccess, - logSize: microserviceData.logSize, - volumeMappings: microserviceData.volumeMappings, - }; - - const images = [ - {archId: 1, containerImage: 'hello-world'}, - {archId: 2, containerImage: 'hello-world'}, - ] - - const newMicroserviceUuid = microserviceUuid; - const oldMicroservice = { - uuid: newMicroserviceUuid, - name: 'oldName', - config: microserviceData.config, - catalogItemId: microserviceData.catalogItemId, - applicationId: microserviceData.applicationId, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: !microserviceData.rootHostAccess, - logSize: microserviceData.logSize, - userId: user.id, - catalogItem: { - images - } - }; - - const microserviceUpdateData = { - uuid: oldMicroservice.uuid, - rootHostAccess: microserviceData.rootHostAccess, - images - } - - const newMicroservice = { - ...oldMicroservice, - rootHostAccess: microserviceUpdateData.rootHostAccess, - } - - const randomString = 'randomString' - - def('subject', () => $subject.updateMicroserviceEndPoint(microserviceUuid, microserviceData, user, isCLI, transaction)); - def('validatorResponse', () => Promise.resolve(true)); - def('deleteUndefinedFieldsResponse', () => microserviceUpdateData); - def('oldMicroserviceResponse', () => Promise.resolve(oldMicroservice)) - def('newMicroserviceResponse', () => Promise.resolve(newMicroservice)) - def('findRegistryResponse', () => Promise.resolve({})) - def('findCatalogItem', () => Promise.resolve({ images })) - def('findFogResponse', () => Promise.resolve({archId: 1, uuid: microserviceData.iofogUuid })) - def('findRelatedExtraHostsResponse', () => Promise.resolve([])) - def('catalogResponse', () => Promise.resolve(images)) - def('updateResponse',() => Promise.resolve()) - - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse); - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse); - $sandbox.stub(AppHelper, 'generateRandomString').returns(randomString); - $sandbox.stub(ChangeTrackingService, 'update') - $sandbox.stub(MicroserviceManager, 'findOneWithCategory').returns($oldMicroserviceResponse) - $sandbox.stub(MicroserviceManager, 'updateAndFind').returns($newMicroserviceResponse) - $sandbox.stub(RegistryManager, 'findOne').returns($findRegistryResponse) - $sandbox.stub(CatalogService, 'getCatalogItem').returns($findCatalogItem) - $sandbox.stub(MicroserviceStatusManager, 'update').returns($updateResponse) - $sandbox.stub(CatalogItemImageManager, 'findAll').returns($catalogResponse) - const stub = $sandbox.stub(ioFogManager, 'findOne') - stub.withArgs({isDefault: true}).returns(Promise.resolve({ - uuid: 'defaultFogUuid', - isDefault: true, - isSystem: true - })) - stub.returns($findFogResponse) - $sandbox.stub(ioFogService, 'getFog').returns($findFogResponse) - $sandbox.stub(MicroserviceExtraHostManager, 'findAll').returns($findRelatedExtraHostsResponse) - }); - - it('calls Validator#validate() with correct args', async () => { - await $subject; - expect(Validator.validate).to.have.been.calledWith(microserviceData, - Validator.schemas.microserviceUpdate); - }); - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)); - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error); - }) - }); - - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject; - const microserviceToUpdate = { - name: microserviceData.name, - config: sinon.match.string, - images: microserviceData.images, - catalogItemId: microserviceData.catalogItemId, - rebuild: microserviceData.rebuild, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: microserviceData.rootHostAccess, - logSize: (microserviceData.logSize || Constants.MICROSERVICE_DEFAULT_LOG_SIZE) * 1, - registryId: microserviceData.registryId, - volumeMappings: microserviceData.volumeMappings, - env: microserviceData.env, - cmd: microserviceData.cmd, - ports: microserviceData.ports, - } - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(microserviceToUpdate); - }); - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('should update the microservice', async () => { - await $subject - expect(MicroserviceManager.updateAndFind).to.have.been.calledWith(query, microserviceUpdateData, transaction) - expect(ChangeTrackingService.update).to.have.been.calledWith(newMicroservice.iofogUuid, ChangeTrackingService.events.microserviceRouting, transaction) - }) - - context('when the microservice could not be found', () => { - def('oldMicroserviceResponse', () => Promise.resolve(null)) - - it('should error with NotFound', async () => { - try{ - await $subject - } catch(e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - return expect(true).to.eql(false) - }) - }) - - context('when the registry could not be found', () => { - def('deleteUndefinedFieldsResponse', () => ({ - uuid: microserviceUuid, - registryId: 5, - })) - def('findRegistryResponse', () => Promise.resolve(null)) - - it('should error with NotFound', async () => { - try{ - await $subject - } catch(e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - return expect(true).to.eql(false) - }) - }) - - context('when there are local related extra hosts', () => { - const relatedExtraHosts = [{ - targetFogUuid: newMicroservice.iofogUuid - }] - def('findRelatedExtraHostsResponse', () => Promise.resolve(relatedExtraHosts)) - beforeEach(() => { - $sandbox.stub(MicroserviceExtraHostManager, 'updateOriginMicroserviceChangeTracking') - }) - - it('Should not update related extraHost', async () => { - await $subject - expect(MicroserviceExtraHostManager.updateOriginMicroserviceChangeTracking).not.to.have.been.called - }) - - context('when microservice is moved to another agent', () => { - const relatedExtraHosts = [{ - targetFogUuid: 'previousUuid', - save: () => {} - }] - const extraHostFog = {uuid: newMicroservice.iofogUuid, host: '1.2.3.4', archId: 1} - - context('when there is no valid image', () => { - const catalogItemNoImages = { - id: 3, - images: [] - } - def('findCatalogItem', () => Promise.resolve(catalogItemNoImages)) - it('Should throw validation error', async () => { - try { - await $subject - } catch (e) { - expect(e).to.be.instanceOf(Errors.ValidationError) - } - }) - }) - def('findRelatedExtraHostsResponse', () => Promise.resolve(relatedExtraHosts)) - def('findRelatedExtraHostFogResponse', () => Promise.resolve(extraHostFog)) - def('findFogResponse', () => Promise.resolve(extraHostFog)) - - it('Should update related extraHost', async () => { - await $subject - for (const e of relatedExtraHosts) { - expect(MicroserviceExtraHostManager.updateOriginMicroserviceChangeTracking).to.have.been.calledWith({...e, targetFogUuid: newMicroservice.iofogUuid, value: '1.2.3.4'}, transaction) - } - }) - - }) - - }) - - context('when microservice is moved to another agent', () => { - const oldFog = { - uuid: 'oldUuid' - } - const newFog = { - uuid: 'newFogUuid', - archId: 1 - } - const portMappings = [] - def('oldMicroserviceResponse', () => Promise.resolve({ - ...oldMicroservice, - iofogUuid: oldFog.uuid, - getPorts: () => Promise.resolve([]) - })) - const newMicroservice = { - ...microserviceUpdateData, - iofogUuid: newFog.uuid, - } - const updatedNewMicroservice = { - ...newMicroservice, - getPorts: () => Promise.resolve([]) - } - def('newMicroserviceResponse', () => Promise.resolve(updatedNewMicroservice)) - def('deleteUndefinedFieldsResponse', () => newMicroservice) - def('getNewAgentMicroserviceReponse', () => Promise.resolve([])) - def('newAgentPublicPortsResponse', () => Promise.resolve([])) - def('getPortsResponse', () => Promise.resolve([])) - def('findFogResponse', () => Promise.resolve({ ...newFog, getMicroservice: () => $getNewAgentMicroserviceReponse })) - - beforeEach(() => { - $sandbox.stub(MicroservicePublicPortManager, 'findAll').returns($newAgentPublicPortsResponse) - $sandbox.stub(updatedNewMicroservice, 'getPorts').returns($getPortsResponse) - }) - - it('should look for ports to move', async () => { - await $subject - expect(updatedNewMicroservice.getPorts).to.have.been.called - }) - - context('when there are ports to move', async () => { - const portMappings = [{ - internalPort: 1, - externalPort: 1, - }] - def('getPortsResponse', () => Promise.resolve(portMappings)) - - beforeEach(() => { - $sandbox.stub(MicroservicePublicPortManager, 'updateOrCreate') - }) - - it('should not move any proxy microservice', async () => { - await $subject - return expect(MicroservicePublicPortManager.updateOrCreate).not.to.have.been.called - }) - - context('when there is a public port', async () => { - const localProxyMsvcUUID = 'localProxyMsvcUUID' - const publicPort = { - id: 42, - queueName: 'testqueue', - protocol: 'http', - localProxyId: localProxyMsvcUUID - } - const publicPortMapping = { - portInternal: 2, - portExternal: 2, - isPublic: true, - getPublicPort: () => Promise.resolve({...publicPort, toJSON: () => publicPort}) - } - const portMappings = [{ - internalPort: 1, - externalPort: 1, - }, publicPortMapping] - - const localMapping = `amqp:${publicPort.queueName}=>${publicPort.protocol}:${publicPortMapping.portExternal}` - - const proxyMsvc = { - uuid: localProxyMsvcUUID, - catalogItemId: 15, - config: JSON.stringify({ - mappings: [localMapping, `fake:queue:because:test:update`] - }) - } - const agentRouter = { - host: 'agentRouterHost', - messagingPort: 5672 - } - const networkRouter = { - host: agentRouter.host, - port: agentRouter.messagingPort - } - const newProxyMsvc = {uuid: 'newProxyUuid'} - def('getPortsResponse', () => Promise.resolve(portMappings)) - - beforeEach(() => { - const msvcStub = $sandbox.stub(MicroserviceManager, 'findOne') - msvcStub.withArgs({uuid: localProxyMsvcUUID}).returns(Promise.resolve(proxyMsvc)) // Current Proxy - msvcStub.returns(Promise.resolve(null)) // Dest proxy - $sandbox.stub(MicroserviceManager, 'updateIfChanged') - $sandbox.stub(MicroserviceManager, 'delete') - $sandbox.stub(MicroserviceManager, 'create').returns(Promise.resolve(newProxyMsvc)) - $sandbox.stub(RouterManager, 'findOne').returns(Promise.resolve(agentRouter)) - }) - - it('Should update the public port and move the proxy msvc', async () => { - const proxyMicroserviceData = { - uuid: sinon.match.string, - name: 'Proxy', - config: JSON.stringify({ - mappings: [localMapping], - networkRouter - }), - catalogItemId: 15, - iofogUuid: newFog.uuid, - rootHostAccess: true, - registryId: 1, - userId: user.id - } - await $subject - expect(MicroserviceManager.updateIfChanged).to.have.been.calledWith( - {uuid: proxyMsvc.uuid}, - {config: JSON.stringify({ - mappings: [`fake:queue:because:test:update`] - }) - }, - transaction) - expect(MicroserviceManager.create).to.have.been.calledWith(proxyMicroserviceData, transaction) - expect(MicroserviceManager.delete).not.to.have.been.called - expect(MicroservicePublicPortManager.updateOrCreate).to.have.been.calledWith({ id: publicPort.id }, publicPort, transaction) - }) - }) - }) - }) - - context('when name is changed', () => { - const name = 'newName' - def('deleteUndefinedFieldsResponse', () => ({...microserviceUpdateData, name})) - def('findMicroserviceByNameResponse', () => Promise.resolve(null)) - beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findOne').returns($findMicroserviceByNameResponse) - }) - - it('should check for duplicate name', async () => { - const where = microserviceUuid - ? { - name: name, - uuid: { [Op.ne]: microserviceUuid }, - delete: false, - userId: user.id, - applicationId: microserviceData.applicationId - } - : { - name: name, - userId: user.id, - delete: false, - applicationId: microserviceData.applicationId - } - await $subject - expect(MicroserviceManager.findOne).to.have.been.calledWith(where, transaction) - }) - - context('Where name is duplicated', () => { - def('findMicroserviceByNameResponse', () => Promise.resolve({name})) - it('should fail with DuplicatePropertyError', async () => { - try{ - await $subject - } catch(e) { - return expect(e).to.be.instanceOf(Errors.DuplicatePropertyError) - } - return expect(true).to.eql(false) - }) - }) - }) - - context('when volume mappings are updated', () => { - const volumeMappings = [ - { - hostDestination: 'hd1', - containerDestination: 'cd1', - accessMode: 'rw' - }, - { - hostDestination: 'hd2', - containerDestination: 'cd2', - accessMode: 'rw' - } - ] - def('deleteUndefinedFieldsResponse', () => ({...microserviceUpdateData, volumeMappings})) - - beforeEach(() => { - $sandbox.stub(VolumeMappingManager, 'delete') - $sandbox.stub(VolumeMappingManager, 'create') - }) - - it('should delete old mappings and create new ones', async () => { - await $subject - expect(VolumeMappingManager.delete).to.have.been.calledWith({microserviceUuid}, transaction) - for (const mapping of volumeMappings) { - const volumeMappingObj = { - microserviceUuid: microserviceUuid, - hostDestination: mapping.hostDestination, - containerDestination: mapping.containerDestination, - accessMode: mapping.accessMode, - type: 'bind' - } - expect(VolumeMappingManager.create).to.have.been.calledWith(volumeMappingObj, transaction) - } - }) - }) - - context('when env are updated', () => { - const env = [ - { - key: 'k1', - value: 'v1', - }, - { - key: 'k2', - value: 'v2', - } - ] - def('deleteUndefinedFieldsResponse', () => ({...microserviceUpdateData, env})) - - beforeEach(() => { - $sandbox.stub(MicroserviceEnvManager, 'delete') - $sandbox.stub(MicroserviceEnvManager, 'create') - }) - - it('should delete old env and create new ones', async () => { - await $subject - expect(MicroserviceEnvManager.delete).to.have.been.calledWith({microserviceUuid}, transaction) - for (const e of env) { - const envObj = { - microserviceUuid: microserviceUuid, - key: e.key, - value: e.value - } - expect(MicroserviceEnvManager.create).to.have.been.calledWith(envObj, transaction) - } - }) - }) - - context('when cmd are updated', () => { - const cmd = ['a1', 'a2'] - def('deleteUndefinedFieldsResponse', () => ({...microserviceUpdateData, cmd})) - - beforeEach(() => { - $sandbox.stub(MicroserviceArgManager, 'delete') - $sandbox.stub(MicroserviceArgManager, 'create') - }) - - it('should delete old env and create new ones', async () => { - await $subject - expect(MicroserviceArgManager.delete).to.have.been.calledWith({microserviceUuid}, transaction) - for (const a of cmd) { - const envObj = { - microserviceUuid: microserviceUuid, - cmd: a, - } - expect(MicroserviceArgManager.create).to.have.been.calledWith(envObj, transaction) - } - }) - }) - - context('when catalog item id is updated', () => { - const catalogItemId = 84 - const catalogItem = { - registryId: 2, - images - } - def('deleteUndefinedFieldsResponse', () => ({...microserviceUpdateData, catalogItemId})) - def('findCatalogItem', () => Promise.resolve(catalogItem)) - def('oldMicroserviceResponse', () => Promise.resolve({...oldMicroservice, catalogItemId: catalogItemId - 1})) - beforeEach(() => { - $sandbox.stub(CatalogItemImageManager, 'delete') - }) - - context('when there is no valid image', () => { - const catalogItemNoImages = { - ...catalogItem, - images: [] - } - def('findCatalogItem', () => Promise.resolve(catalogItemNoImages)) - it('Should throw validation error', async () => { - try { - await $subject - } catch (e) { - expect(e).to.be.instanceOf(Errors.ValidationError) - } - }) - }) - - it('Should delete microservice images', async () => { - await $subject - return expect(CatalogItemImageManager.delete).to.have.been.calledWith({ - microserviceUuid - }, transaction) - }) - - it('Should update with proper registryId', async () => { - await $subject - expect(MicroserviceManager.updateAndFind).to.have.been.calledWith(query, {...microserviceUpdateData, rebuild: true, catalogItemId, registryId: catalogItem.registryId}, transaction) - }) - }) - - context('when images are updated', () => { - const images = [ - {archId: 1, containerImage: 'newImage:x86'}, - {archId: 2, containerImage: 'newImage:arm'}, - ] - registryId = 1 - const microserviceUpdateDataWithImages = {...microserviceUpdateData, images, registryId} - def('deleteUndefinedFieldsResponse', () => (microserviceUpdateDataWithImages)) - - beforeEach(() => { - $sandbox.stub(CatalogItemImageManager, 'delete') - $sandbox.stub(CatalogItemImageManager, 'bulkCreate') - }) - - context('when there is no valid image', () => { - const images = [ - {} - ] - registryId = 1 - const microserviceUpdateDataWithImages = {...microserviceUpdateData, images, registryId} - def('deleteUndefinedFieldsResponse', () => (microserviceUpdateDataWithImages)) - it('Should throw validation error', async () => { - try { - await $subject - } catch (e) { - expect(e).to.be.instanceOf(Errors.ValidationError) - } - }) - }) - - it('should update images', async () => { - await $subject - const newImages = [] - for (const img of images) { - const newImg = Object.assign({}, img) - newImg.microserviceUuid = microserviceUuid - newImages.push(newImg) - } - expect(CatalogItemImageManager.delete).to.have.been.calledWith({microserviceUuid}, transaction) - expect(CatalogItemImageManager.bulkCreate).to.have.been.calledWith(newImages, transaction) - }) - - it('should set rebuild flag', async () => { - await $subject - expect(MicroserviceManager.updateAndFind).to.have.been.calledWith(query, {...microserviceUpdateDataWithImages, rebuild: true}, transaction) - }) - - context('When there are no catalog item and the image array is empty', () => { - def('oldMicroserviceResponse', () => Promise.resolve({...oldMicroservice, catalogItemId: null})) - def('deleteUndefinedFieldsResponse', () => ({...microserviceUpdateDataWithImages, images: []})) - - it('should error with ValidationError', async () => { - try{ - await $subject - } catch(e){ - return expect(e).to.be.instanceOf(Errors.ValidationError) - } - return expect(true).to.eql(false) - }) - }) - - }) - - }) + const msvcUuid = 'msvc-uuid' + const existing = buildMicroserviceRecord({ + uuid: msvcUuid, + name: 'immutable-name', + catalogItem: null }) - }); - - describe('.deleteMicroserviceEndPoint()', () => { - const transaction = {}; - const microserviceUuid = 'msvcToDeleteUUID' - const isCLI = false + def('subject', () => $service.updateMicroserviceEndPoint(msvcUuid, $updateData, isCLI, transaction)) + def('updateData', () => ({ config: '{"k":"v"}' })) - const microserviceData = { - uuid: microserviceUuid, - iofogUuid: 'msvciofoguuid' - } - - const ServiceManager = require('../../../src/data/managers/service-manager') - const NatsAuthService = require('../../../src/services/nats-auth-service') - const MicroservicePortService = require('../../../src/services/microservice-ports/microservice-port') - const RbacServiceAccountManager = require('../../../src/data/managers/rbac-service-account-manager') - - def('subject', () => $subject.deleteMicroserviceEndPoint(microserviceUuid, microserviceData, isCLI, transaction)) - def('findMicroserviceResponse', () => Promise.resolve(microserviceData)) - def('findPortMappings', () => Promise.resolve([])) - beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findOneWithStatusAndCategory').returns($findMicroserviceResponse) - $sandbox.stub(MicroservicePortManager, 'findAll').returns($findPortMappings) - $sandbox.stub(MicroserviceManager, 'delete') - $sandbox.stub(ChangeTrackingService, 'update') + stubUpdateMicroserviceDeps($sandbox, existing) }) - const stubDeleteDependencies = ({ keepPortDeletion = false } = {}) => { - $sandbox.stub(ServiceManager, 'findOne').resolves(null) - $sandbox.stub(NatsAuthService, 'revokeMicroserviceUser').resolves() - if (!keepPortDeletion) { - $sandbox.stub(MicroservicePortService, 'deletePortMappings').resolves() - } - $sandbox.stub(RbacServiceAccountManager, 'deleteByMicroserviceUuid').resolves() - if (!keepPortDeletion) { - $sandbox.stub(MicroserviceManager, 'update').resolves() - } - } - - it('should delete the microservice', async () => { - stubDeleteDependencies() + it('updates mutable fields', async () => { await $subject - expect(MicroserviceManager.delete).to.have.been.calledWith({uuid: microserviceUuid}, transaction) - return expect(ChangeTrackingService.update).to.have.been.calledWith(microserviceData.iofogUuid, ChangeTrackingService.events.microserviceList, transaction) + expect(MicroserviceManager.updateAndFind).to.have.been.called + expect(ChangeTrackingService.update).to.have.been.called }) - context('when microservice is not found', () => { - def('findMicroserviceResponse', () => Promise.resolve(null)) - it('should fail with NotFound error', async () => { - try { - await $subject - } catch(e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - return expect(true).to.eql(false) - }) - }) + context('when renaming', () => { + def('updateData', () => ({ name: 'new-name' })) - context('when microservice is system', () => { - def('findMicroserviceResponse', () => Promise.resolve({ - catalogItem: { - category: 'SYSTEM' - } - })) - it('should fail with NotFound error', async () => { - try { - await $subject - } catch(e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - return expect(true).to.eql(false) - }) + it('rejects rename attempts', () => expect($subject).to.be.rejectedWith('Microservice Resource Name is immutable')) }) context('when microservice is controller', () => { - def('findMicroserviceResponse', () => Promise.resolve({ - uuid: microserviceUuid, - iofogUuid: microserviceData.iofogUuid, - isController: true - })) - it('should fail with ForbiddenError', async () => { - try { - await $subject - } catch (e) { - return expect(e).to.be.instanceOf(Errors.ForbiddenError) - } - return expect(true).to.eql(false) - }) - }) - - context('when there are ports', () => { - const publicPort = { - id: 1, - queueName: 'queuename', - localProxyId: 15, - remoteProxyId: null, - } - const portMappings = [{ - id: 1, - portExternal: 1, - portInternal: 1 - }, { - id: 2, - portExternal: 2, - portInternal: 2, - isPublic: true, - getPublicPort: () => Promise.resolve(publicPort) - }] - const localProxyMsvc = { - uuid: 'proxyuuid', - iofogUuid: 'proxyiofoguuid', - config: `{"mappings": ["amqp:${publicPort.queueName}=>http:${portMappings[1].portExternal}"]}` - } - const remoteProxyMsvc = null // Simulates K8s env - def('findPortMappings', () => Promise.resolve(portMappings)) - beforeEach(() => { - stubDeleteDependencies({ keepPortDeletion: true }) - $sandbox.stub(MicroservicePortManager, 'delete') - $sandbox.stub(MicroserviceManager, 'update') - $sandbox.stub(MicroserviceManager, 'findOne') - .withArgs({uuid: publicPort.localProxyId}, transaction).returns(Promise.resolve(localProxyMsvc)) - .withArgs({uuid: publicPort.remoteProxyId}, transaction).returns(Promise.resolve(remoteProxyMsvc)) - }) - - it('should delete ports and proxy msvc', async () => { - await $subject - // Private port - expect(MicroservicePortManager.delete).to.have.been.calledWith({id: portMappings[0].id}, transaction) - expect(MicroserviceManager.update).to.have.been.calledWith({ uuid: microserviceData.uuid }, {rebuild: true}, transaction) - expect(ChangeTrackingService.update).to.have.been.calledWith(microserviceData.iofogUuid, ChangeTrackingService.events.microserviceCommon, transaction) - - // Public port - expect(MicroserviceManager.findOne).to.have.been.calledWith({uuid: publicPort.localProxyId}, transaction) - expect(MicroserviceManager.findOne).to.have.been.calledWith({uuid: publicPort.remoteProxyId}, transaction) - expect(MicroserviceManager.delete).to.have.been.calledWith({uuid: localProxyMsvc.uuid}, transaction) - expect(ChangeTrackingService.update).to.have.been.calledWith(localProxyMsvc.iofogUuid, ChangeTrackingService.events.microserviceConfig, transaction) - expect(MicroservicePortManager.delete).to.have.been.calledWith({id: portMappings[1].id}, transaction) - }) - }) - - }); - - describe('.createPortMappingEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const microserviceUuid = 'testMicroserviceUuid' - - const microserviceData = { - 'uuid': microserviceUuid, - 'name': 'name2', - 'config': 'string', - 'catalogItemId': 15, - 'applicationId': 16, - 'iofogUuid': 'testIofogUuid', - 'rootHostAccess': true, - 'logSize': 1, - 'volumeMappings': [ - { - 'hostDestination': '/var/dest', - 'containerDestination': '/var/dest', - 'accessMode': 'rw', - }, - ], - 'ports': [ - { - 'internal': 1, - 'external': 1, - 'publicMode': false, - }, - ], - } - const newMicroservice = { - uuid: microserviceUuid, - name: microserviceData.name, - config: microserviceData.config, - catalogItemId: microserviceData.catalogItemId, - applicationId: microserviceData.applicationId, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: microserviceData.rootHostAccess, - logSize: microserviceData.logSize, - registryId: 1, - userId: user.id, - } - - const portMappingData = [ - { - 'internal': 1, - 'external': 1, - 'publicMode': false, - }, - ] - - const where = isCLI - ? { uuid: microserviceUuid } - : { uuid: microserviceUuid, userId: user.id } - - const mappingData = { - isProxy: false, - isPublic: false, - isUdp: false, - portInternal: portMappingData.internal, - portExternal: portMappingData.external, - userId: microserviceData.userId, - microserviceUuid: microserviceData.uuid, - } - - const fog = { - uuid: microserviceData.iofogUuid, - name: 'testFog' - } - - def('subject', () => $subject.createPortMappingEndPoint(microserviceUuid, portMappingData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findMicroserviceResponse', () => Promise.resolve(microserviceData)) - def('findMicroservicePortResponse', () => Promise.resolve()) - def('createMicroservicePortResponse', () => Promise.resolve()) - def('updateMicroserviceResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse', () => Promise.resolve()) - def('findOneFogResponse', () => Promise.resolve({...fog, getMicroservice: () => Promise.resolve([])})) - def('findPublicPortsResponse', () => Promise.resolve([])) - def('findRelatedExtraHosts', () => Promise.resolve([])) - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(MicroserviceManager, 'findOne').returns($findMicroserviceResponse) - $sandbox.stub(MicroservicePortManager, 'findOne').returns($findMicroservicePortResponse) - $sandbox.stub(MicroservicePortManager, 'create').returns($createMicroservicePortResponse) - $sandbox.stub(MicroserviceManager, 'update').returns($updateMicroserviceResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findOneFogResponse) - $sandbox.stub(MicroservicePublicPortManager, 'findAll').returns($findPublicPortsResponse) - $sandbox.stub(MicroserviceExtraHostManager, 'findAll').returns($findRelatedExtraHosts) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(portMappingData, Validator.schemas.portsCreate) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls MicroserviceManager#findOne() with correct args', async () => { - await $subject - expect(MicroserviceManager.findOne).to.have.been.calledWith(where, transaction) - }) - - context('when MicroserviceManager#findOne() fails', () => { - def('findMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + MicroserviceManager.findOneWithCategory.resolves({ + ...existing, + isController: true, + catalogItem: null, + getPorts: () => Promise.resolve([]), + getImages: () => Promise.resolve([]) }) }) - context('when MicroserviceManager#findOne() succeeds', () => { - it('calls MicroservicePortManager#findOne() with correct args', async () => { - await $subject - expect(MicroservicePortManager.findOne).to.have.been.calledWith({ - microserviceUuid: microserviceUuid, - [Op.or]: - [ - { - portInternal: portMappingData.internal, - }, - { - portExternal: portMappingData.external, - }, - ], - }, transaction) - }) - - context('when MicroservicePortManager#findOne() fails', () => { - def('findMicroservicePortResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroservicePortManager#findOne() succeeds', () => { - it('calls MicroservicePortManager#create() with correct args', async () => { - await $subject - expect(MicroservicePortManager.create).to.have.been.calledWith(mappingData, transaction) - }) - - context('when MicroservicePortManager#create() fails', () => { - def('createMicroservicePortResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroservicePortManager#create() succeeds', () => { - it('calls MicroserviceManager#update() with correct args', async () => { - await $subject - const updateRebuildMs = { - rebuild: true, - } - expect(MicroserviceManager.update).to.have.been.calledWith({ - uuid: newMicroservice.uuid, - }, updateRebuildMs, transaction) - }) - - context('when MicroserviceManager#update() fails', () => { - def('updateMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#update() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(microserviceData.iofogUuid, - ChangeTrackingService.events.microserviceConfig, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).eventually.equals(undefined) - }) - }) - }) - }) - }) - }) + it('rejects updates', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) }) - }) - - describe('microservice runtime validation', () => { - const transaction = {} - const application = { name: 'my-app', id: 42, active: true } - const fogUuid = 'fog-uuid' - - describe('.createMicroserviceEndPoint()', () => { - def('fog', () => ({ - uuid: fogUuid, - name: 'agent-1', - archId: 1, - availableRuntimes: '["docker"]' - })) - def('microserviceData', () => ({ - name: 'my-msvc', - application: application.name, - iofogUuid: fogUuid, - runtime: 'edgelet', - images: [{ archId: 1, containerImage: 'hello-world' }] - })) - def('subject', () => MicroservicesService.createMicroserviceEndPoint($microserviceData, isCLI, transaction)) + context('when microservice is system catalog', () => { beforeEach(() => { - $sandbox.stub(Validator, 'validate').resolves(true) - $sandbox.stub(ApplicationManager, 'findOne').resolves(application) - $sandbox.stub(ioFogManager, 'findOne').resolves($fog) - }) - - context('when runtime is not in agent availableRuntimes', () => { - it('rejects the request', () => { - return expect($subject).to.be.rejectedWith( - Errors.ValidationError, - /Runtime 'edgelet' is not available on agent 'agent-1'/ - ) + MicroserviceManager.findOneWithCategory.resolves({ + ...existing, + isController: false, + catalogItem: { category: 'SYSTEM' }, + getPorts: () => Promise.resolve([]), + getImages: () => Promise.resolve([]) }) }) - context('when agent has no availableRuntimes and runtime is set', () => { - def('fog', () => ({ - uuid: fogUuid, - name: 'agent-1', - archId: 1, - availableRuntimes: '' - })) - - it('rejects the request', () => { - return expect($subject).to.be.rejectedWith( - Errors.ValidationError, - /Runtime 'edgelet' is not available on agent 'agent-1'/ - ) - }) - }) + it('rejects updates', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) }) }) - describe('serviceAccount volume immutability', () => { - const transaction = {} - const microserviceUuid = 'msvc-uuid' - const microserviceName = 'my-msvc' - const saContainerDestination = '/var/run/secrets/edgelet.iofog.org/serviceaccount' - const MicroservicePortService = require('../../../src/services/microservice-ports/microservice-port') - const MicroserviceExecStatusManager = require('../../../src/data/managers/microservice-exec-status-manager') - const CatalogItemImageManager = require('../../../src/data/managers/catalog-item-image-manager') - const RbacRoleManager = require('../../../src/data/managers/rbac-role-manager') - const RbacServiceAccountManager = require('../../../src/data/managers/rbac-service-account-manager') - - describe('.createMicroserviceEndPoint()', () => { - const application = { name: 'my-app', id: 42, active: true } - const fog = { uuid: 'fog-uuid', name: 'agent-1', archId: 1, availableRuntimes: '["docker"]' } - - def('subject', () => MicroservicesService.createMicroserviceEndPoint($microserviceData, isCLI, transaction)) - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').resolves(true) - $sandbox.stub(ApplicationManager, 'findOne').resolves(application) - $sandbox.stub(ioFogManager, 'findOne').resolves(fog) - $sandbox.stub(MicroserviceManager, 'findOne').callsFake((where) => { - if (where && where.uuid === microserviceUuid) { - return Promise.resolve({ - uuid: microserviceUuid, - name: microserviceName, - applicationId: application.id - }) - } - return Promise.resolve(null) - }) - $sandbox.stub(AppHelper, 'generateRandomString').returns(microserviceUuid) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((obj) => obj) - $sandbox.stub(MicroserviceManager, 'create').resolves({ - uuid: microserviceUuid, - name: microserviceName, - applicationId: application.id, - iofogUuid: fog.uuid - }) - $sandbox.stub(MicroservicePortService, 'validatePortMappings').resolves() - $sandbox.stub(RegistryManager, 'findOne').resolves({ id: 1 }) - $sandbox.stub(MicroservicePortManager, 'findOne').resolves(null) - $sandbox.stub(MicroservicePortManager, 'create').resolves({ id: 1 }) - $sandbox.stub(MicroserviceStatusManager, 'create').resolves() - $sandbox.stub(MicroserviceExecStatusManager, 'create').resolves() - $sandbox.stub(CatalogItemImageManager, 'bulkCreate').resolves() - $sandbox.stub(ChangeTrackingService, 'update').resolves() - $sandbox.stub(VolumeMappingManager, 'bulkCreate').resolves() - $sandbox.stub(VolumeMappingManager, 'findOne').resolves(null) - $sandbox.stub(VolumeMappingManager, 'create').resolves({ uuid: 'sa-volume-uuid' }) - $sandbox.stub(RbacRoleManager, 'getRoleWithRules').resolves({ name: 'microservice' }) - $sandbox.stub(RbacServiceAccountManager, 'findOneByMicroserviceUuid').resolves(null) - $sandbox.stub(RbacServiceAccountManager, 'createServiceAccount').resolves({ name: microserviceName }) - }) - - context('when user supplies a serviceAccount volume mapping', () => { - def('microserviceData', () => ({ - name: microserviceName, - application: application.name, - iofogUuid: fog.uuid, - images: [{ archId: 1, containerImage: 'hello-world' }], - volumeMappings: [{ - hostDestination: microserviceName, - containerDestination: saContainerDestination, - accessMode: 'ro', - type: 'serviceAccount' - }] - })) - - it('rejects the request', () => { - return expect($subject).to.be.rejectedWith( - Errors.ValidationError, - /serviceAccount are system-managed/ - ) - }) - }) + describe('.deleteMicroserviceEndPoint()', () => { + const msvcUuid = 'msvc-uuid' + const microservice = buildMicroserviceRecord({ + uuid: msvcUuid, + catalogItem: null + }) - context('when user does not supply a serviceAccount volume mapping', () => { - def('microserviceData', () => ({ - name: microserviceName, - application: application.name, - iofogUuid: fog.uuid, - images: [{ archId: 1, containerImage: 'hello-world' }], - volumeMappings: [{ - hostDestination: '/var/dest', - containerDestination: '/var/dest', - accessMode: 'rw', - type: 'bind' - }] - })) + def('subject', () => $service.deleteMicroserviceEndPoint(msvcUuid, {}, false, transaction)) - it('auto-injects the serviceAccount volume after user mappings', async () => { - await $subject - expect(VolumeMappingManager.create).to.have.been.calledWith({ - microserviceUuid, - hostDestination: microserviceName, - containerDestination: saContainerDestination, - accessMode: 'ro', - type: 'serviceAccount' - }, transaction) - }) - }) + beforeEach(() => { + $sandbox.stub(MicroserviceManager, 'findOneWithStatusAndCategory').resolves(microservice) + $sandbox.stub(ServiceManager, 'findOne').resolves(null) + $sandbox.stub(RbacServiceAccountManager, 'deleteByMicroserviceUuid').resolves() + $sandbox.stub(MicroserviceManager, 'update').resolves() + $sandbox.stub(NatsAuthService, 'revokeMicroserviceUser').resolves() + $sandbox.stub(MicroservicePortService, 'deletePortMappings').resolves() + $sandbox.stub(MicroserviceManager, 'delete').resolves() + $sandbox.stub(ChangeTrackingService, 'update').resolves() }) - describe('.createVolumeMappingEndPoint()', () => { - def('volumeMappingData', () => ({ - hostDestination: microserviceName, - containerDestination: saContainerDestination, - accessMode: 'ro', - type: 'serviceAccount' - })) - def('subject', () => MicroservicesService.createVolumeMappingEndPoint( - microserviceUuid, - $volumeMappingData, - isCLI, + it('deletes a user microservice', async () => { + await $subject + expect(MicroserviceManager.delete).to.have.been.calledWith({ uuid: msvcUuid }, transaction) + expect(ChangeTrackingService.update).to.have.been.calledWith( + microservice.iofogUuid, + ChangeTrackingService.events.microserviceList, transaction - )) + ) + }) + context('when microservice is controller', () => { beforeEach(() => { - $sandbox.stub(Validator, 'validate').resolves(true) - $sandbox.stub(MicroserviceManager, 'findMicroserviceOnGet').resolves({ uuid: microserviceUuid }) + MicroserviceManager.findOneWithStatusAndCategory.resolves({ + ...microservice, + isController: true + }) }) - it('rejects serviceAccount volume creation by users', () => { - return expect($subject).to.be.rejectedWith( - Errors.ValidationError, - /serviceAccount are system-managed/ - ) - }) + it('forbids deletion via REST', () => expect($subject).to.be.rejectedWith(Errors.ForbiddenError)) }) - describe('.deleteVolumeMappingEndPoint()', () => { - const saVolumeUuid = 'sa-volume-uuid' - def('subject', () => MicroservicesService.deleteVolumeMappingEndPoint( - microserviceUuid, - saVolumeUuid, - isCLI, - transaction - )) - + context('when microservice is system catalog', () => { beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findOne').resolves({ uuid: microserviceUuid }) - $sandbox.stub(VolumeMappingManager, 'findOne').resolves({ - uuid: saVolumeUuid, - type: 'serviceAccount' + MicroserviceManager.findOneWithStatusAndCategory.resolves({ + ...microservice, + catalogItem: { category: 'SYSTEM' } }) - $sandbox.stub(VolumeMappingManager, 'delete').resolves(1) - }) - - it('rejects deletion of serviceAccount volume mappings', () => { - return expect($subject).to.be.rejectedWith( - Errors.ValidationError, - /serviceAccount are system-managed/ - ) }) - it('does not call VolumeMappingManager#delete()', async () => { - try { - await $subject - } catch (error) { - // expected - } - expect(VolumeMappingManager.delete).to.not.have.been.called - }) + it('forbids deletion via REST', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) + }) - describe('.updateMicroserviceEndPoint()', () => { - const microservice = { - uuid: microserviceUuid, - name: microserviceName, - applicationId: 42, - registryId: 1, - iofogUuid: 'fog-uuid', - schedule: 0, - catalogItem: null, - getImages: () => Promise.resolve([{ archId: 1, containerImage: 'hello-world' }]), - getPorts: () => Promise.resolve([]) - } - - def('subject', () => MicroservicesService.updateMicroserviceEndPoint( - microserviceUuid, - $microserviceData, - isCLI, - transaction - )) - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').resolves(true) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((obj) => obj) - $sandbox.stub(MicroserviceManager, 'findOne').resolves(microservice) - $sandbox.stub(MicroserviceManager, 'findOneWithCategory').resolves(microservice) - $sandbox.stub(CatalogItemImageManager, 'findAll').resolves([]) - $sandbox.stub(ApplicationManager, 'findOne').resolves({ id: 42, natsAccess: false }) - $sandbox.stub(RegistryManager, 'findOne').resolves({ id: 1 }) - $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid: 'fog-uuid', archId: 1 }) - $sandbox.stub(MicroserviceExtraHostManager, 'findAll').resolves([]) - $sandbox.stub(MicroserviceManager, 'updateAndFind').resolves(microservice) - $sandbox.stub(ChangeTrackingService, 'update').resolves() - }) + describe('.createPortMappingEndPoint()', () => { + const msvcUuid = 'msvc-uuid' + const portMappingData = { internal: 8080, external: 18080 } + const microservice = buildMicroserviceRecord({ uuid: msvcUuid }) + const agent = { uuid: 'fog-uuid', name: 'edge-1' } + const createdMapping = { portInternal: 8080, portExternal: 18080 } - context('when patch includes a serviceAccount volume mapping', () => { - def('microserviceData', () => ({ - volumeMappings: [{ - hostDestination: microserviceName, - containerDestination: saContainerDestination, - accessMode: 'ro', - type: 'serviceAccount' - }] - })) + def('subject', () => $service.createPortMappingEndPoint(msvcUuid, portMappingData, isCLI, transaction)) - it('rejects the update', () => { - return expect($subject).to.be.rejectedWith( - Errors.ValidationError, - /serviceAccount are system-managed/ - ) - }) - }) + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(MicroserviceManager, 'findMicroserviceOnGet').resolves(microservice) + $sandbox.stub(MicroserviceManager, 'findOne').resolves(microservice) + $sandbox.stub(FogManager, 'findOne').resolves(agent) + $sandbox.stub(MicroservicePortService, 'validatePortMapping').resolves() + $sandbox.stub(MicroservicePortService, 'createPortMapping').resolves(createdMapping) }) - }) - - describe('controller microservice user guards', () => { - const transaction = {} - const microserviceUuid = 'controller-ms-uuid' - const images = [{ archId: 1, containerImage: 'controller:latest' }] - const microservice = { - uuid: microserviceUuid, - name: 'controller', - applicationId: 16, - iofogUuid: 'system-fog-uuid', - registryId: 1, - isController: true, - catalogItem: { images } - } - describe('.updateMicroserviceEndPoint()', () => { - def('subject', () => MicroservicesService.updateMicroserviceEndPoint( - microserviceUuid, - { config: '{}' }, - false, - transaction - )) + it('validates and delegates port mapping creation', async () => { + const result = await $subject + expect(Validator.validate).to.have.been.calledWith(portMappingData, Validator.schemas.portsCreate) + expect(MicroserviceManager.findMicroserviceOnGet).to.have.been.calledWith({ uuid: msvcUuid }, transaction) + expect(MicroservicePortService.validatePortMapping).to.have.been.calledWith(agent, portMappingData, {}, transaction) + expect(MicroservicePortService.createPortMapping).to.have.been.calledWith(microservice, portMappingData, transaction) + expect(result).to.equal(createdMapping) + }) + context('when microservice is missing', () => { beforeEach(() => { - $sandbox.stub(Validator, 'validate').resolves(true) - $sandbox.stub(MicroserviceManager, 'findOne').resolves(microservice) - $sandbox.stub(MicroserviceManager, 'findOneWithCategory').resolves(microservice) - $sandbox.stub(CatalogItemImageManager, 'findAll').resolves(images) - $sandbox.stub(ApplicationManager, 'findOne').resolves({ id: microservice.applicationId, natsAccess: false }) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((obj) => obj) - $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid: microservice.iofogUuid, archId: 1 }) - $sandbox.stub(ioFogService, 'getFog').resolves({ uuid: microservice.iofogUuid, archId: 1 }) + MicroserviceManager.findOne.resolves(null) }) - it('blocks user update when isController', async () => { - await expect($subject).to.be.rejectedWith(Errors.ValidationError) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) }) diff --git a/test/src/services/nats-auth-service.test.js b/test/src/services/nats-auth-service.test.js index a648acb9..2642dee0 100644 --- a/test/src/services/nats-auth-service.test.js +++ b/test/src/services/nats-auth-service.test.js @@ -10,6 +10,7 @@ const ApplicationManager = require('../../../src/data/managers/application-manag const SecretService = require('../../../src/services/secret-service') const NatsService = require('../../../src/services/nats-service') const NatsAuthService = require('../../../src/services/nats-auth-service') +const { createOperator, createAccount } = require('@nats-io/nkeys') describe('NATS Auth Service', () => { def('sandbox', () => sinon.createSandbox()) @@ -101,13 +102,27 @@ describe('NATS Auth Service', () => { }) context('when existing user has same account but different rule (revoke and reissue)', () => { + const operatorKp = createOperator() + const accountKp = createAccount() + const operatorSeed = new TextDecoder().decode(operatorKp.getSeed()) + const accountSeed = new TextDecoder().decode(accountKp.getSeed()) + beforeEach(() => { $sandbox.stub(NatsUserManager, 'findOne').resolves(existingUserSameAccountDifferentRule) $sandbox.stub(NatsUserManager, 'update').resolves() $sandbox.stub(NatsAccountManager, 'update').resolves() - $sandbox.stub(SecretService, 'getSecretEndpoint').resolves({ data: { seed: 'operator-seed-base64' } }) + $sandbox.stub(SecretService, 'getSecretEndpoint').callsFake((secretName) => { + if (secretName === 'op-seed') { + return Promise.resolve({ data: { seed: operatorSeed } }) + } + if (secretName === 'acc-seed') { + return Promise.resolve({ data: { seed: accountSeed } }) + } + return Promise.resolve(null) + }) $sandbox.stub(SecretService, 'createSecretEndpoint').resolves() $sandbox.stub(SecretService, 'updateSecretEndpointIfChanged').resolves() + $sandbox.stub(NatsAccountRuleManager, 'findOne').resolves({ name: 'default-account' }) const NatsOperatorManager = require('../../../src/data/managers/nats-operator-manager') $sandbox.stub(NatsOperatorManager, 'findOne').resolves({ seedSecretName: 'op-seed' }) }) diff --git a/test/src/services/nats-service.test.js b/test/src/services/nats-service.test.js index 9e42f5db..a353c4de 100644 --- a/test/src/services/nats-service.test.js +++ b/test/src/services/nats-service.test.js @@ -9,7 +9,8 @@ const NatsUserManager = require('../../../src/data/managers/nats-user-manager') const MicroserviceManager = require('../../../src/data/managers/microservice-manager') const VolumeMappingManager = require('../../../src/data/managers/volume-mapping-manager') const VolumeMountService = require('../../../src/services/volume-mount-service') -const ConfigMapManager = require('../../../src/data/managers/config-map-manager') +const ConfigMapService = require('../../../src/services/config-map-service') +const NatsAuthService = require('../../../src/services/nats-auth-service') const SecretService = require('../../../src/services/secret-service') describe('NATS Service', () => { @@ -20,13 +21,14 @@ describe('NATS Service', () => { describe('.cleanupNatsForFog()', () => { const transaction = {} const fog = { uuid: 'fog-1', name: 'local-agent' } - const natsInstance = { id: 77 } + const natsInstance = { id: 77, isLeaf: true, isHub: false } const microservices = [{ uuid: 'ms-1' }] def('subject', () => NatsService.cleanupNatsForFog(fog, transaction)) beforeEach(() => { $sandbox.stub(NatsInstanceManager, 'findByFog').returns(Promise.resolve(natsInstance)) + $sandbox.stub(NatsInstanceManager, 'findAll').returns(Promise.resolve([])) $sandbox.stub(NatsAccountManager, 'findOne').returns(Promise.resolve({ id: 1 })) $sandbox.stub(NatsUserManager, 'findOne').returns(Promise.resolve({ credsSecretName: 'nats-creds-sys-admin-hub' })) $sandbox.stub(NatsConnectionManager, 'delete').returns(Promise.resolve()) @@ -36,8 +38,9 @@ describe('NATS Service', () => { $sandbox.stub(VolumeMountService, 'unlinkVolumeMountEndpoint').returns(Promise.resolve()) $sandbox.stub(VolumeMountService, 'findVolumeMountedFogNodes').returns(Promise.resolve([])) $sandbox.stub(VolumeMountService, 'deleteVolumeMountEndpoint').returns(Promise.resolve()) - $sandbox.stub(ConfigMapManager, 'getConfigMap').returns(Promise.resolve({ name: 'cm' })) - $sandbox.stub(ConfigMapManager, 'deleteConfigMap').returns(Promise.resolve()) + $sandbox.stub(ConfigMapService, 'deleteConfigMapEndpoint').returns(Promise.resolve()) + $sandbox.stub(NatsAuthService, 'getLeafSystemArtifactSecretNames').returns(Promise.resolve(null)) + $sandbox.stub(NatsAuthService, 'deleteLeafSystemArtifactsForFog').returns(Promise.resolve()) $sandbox.stub(SecretService, 'deleteSecretEndpoint').returns(Promise.resolve()) }) @@ -48,7 +51,7 @@ describe('NATS Service', () => { expect(NatsConnectionManager.delete).to.have.been.calledWith({ sourceNats: natsInstance.id }, transaction) expect(NatsConnectionManager.delete).to.have.been.calledWith({ destNats: natsInstance.id }, transaction) expect(NatsInstanceManager.delete).to.have.been.calledWith({ id: natsInstance.id }, transaction) - expect(ConfigMapManager.deleteConfigMap).to.have.been.called + expect(ConfigMapService.deleteConfigMapEndpoint).to.have.been.called expect(SecretService.deleteSecretEndpoint).to.have.been.called }) }) diff --git a/test/src/services/registry-service.test.js b/test/src/services/registry-service.test.js index 8d23bfa1..95bc9f28 100644 --- a/test/src/services/registry-service.test.js +++ b/test/src/services/registry-service.test.js @@ -5,481 +5,269 @@ const RegistryManager = require('../../../src/data/managers/registry-manager') const RegistryService = require('../../../src/services/registry-service') const Validator = require('../../../src/schemas') const AppHelper = require('../../../src/helpers/app-helper') -const ioFogManager = require('../../../src/data/managers/iofog-manager') +const FogManager = require('../../../src/data/managers/iofog-manager') const ChangeTrackingService = require('../../../src/services/change-tracking-service') -const Sequelize = require('sequelize') -const Op = Sequelize.Op +const MicroserviceManager = require('../../../src/data/managers/microservice-manager') +const SecretHelper = require('../../../src/helpers/secret-helper') +const ErrorMessages = require('../../../src/helpers/error-messages') +const Errors = require('../../../src/helpers/errors') + +const transaction = {} +const isCLI = true + +function buildRegistryRecord (fields = {}) { + return { + id: 16, + url: 'https://registry.example.com', + username: 'user', + password: 'encrypted-secret', + isPublic: false, + userEmail: 'user@example.com', + ...fields + } +} + +function stubChangeTrackingDeps (sandbox, { fogUuid = 'fog-uuid' } = {}) { + sandbox.stub(FogManager, 'findAll').resolves([{ uuid: fogUuid }]) + sandbox.stub(ChangeTrackingService, 'update').resolves() +} describe('Registry Service', () => { - def('subject', () => RegistryService) + def('service', () => RegistryService) def('sandbox', () => sinon.createSandbox()) - const isCLI = false - afterEach(() => $sandbox.restore()) describe('.createRegistry()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const registry = { - url: 'testUrl', - username: 'testUsername', - password: 'testPassword', + const registryData = { + url: 'https://registry.example.com', + username: 'user', + password: 'plain-password', isPublic: false, - userEmail: 'testEmail', - requiresCert: false, - certificate: 'testCertificate', - userId: user.id, - } - - const registryCreate = { - url: registry.url, - username: registry.username, - password: registry.password, - isPublic: registry.isPublic, - userEmail: registry.email, - requiresCert: registry.requiresCert, - certificate: registry.certificate, - userId: user.id, + email: 'user@example.com' } + const created = buildRegistryRecord({ id: 16, password: 'plain-password' }) - const ioFogs = [{ - uuid: 'testUuid', - }] - - def('subject', () => $subject.createRegistry(registry, user, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse', () => registryCreate) - def('createRegistryResponse', () => Promise.resolve({ - id: 16, - })) - def('findIoFogsResponse', () => Promise.resolve(ioFogs)) - def('updateChangeTrackingResponse', () => Promise.resolve()) + def('subject', () => $service.createRegistry(registryData, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(RegistryManager, 'create').returns($createRegistryResponse) - $sandbox.stub(ioFogManager, 'findAll').returns($findIoFogsResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + $sandbox.stub(RegistryManager, 'create').resolves(created) + $sandbox.stub(SecretHelper, 'encryptSecret').resolves('encrypted-password') + $sandbox.stub(RegistryManager, 'update').resolves() + stubChangeTrackingDeps($sandbox) }) - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(registry, Validator.schemas.registryCreate) + it('validates input, encrypts password, and returns registry id', async () => { + const result = await $subject + expect(Validator.validate).to.have.been.calledWith(registryData, Validator.schemas.registryCreate) + expect(RegistryManager.create).to.have.been.calledWithMatch({ + url: registryData.url, + username: registryData.username, + userEmail: registryData.email + }, transaction) + expect(SecretHelper.encryptSecret).to.have.been.calledWith( + { value: registryData.password }, + 'registry-16', + 'registry' + ) + expect(ChangeTrackingService.update).to.have.been.calledWith( + 'fog-uuid', + ChangeTrackingService.events.registries, + transaction + ) + expect(result).to.eql({ id: 16 }) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when password is empty', () => { + def('registryData', () => ({ + url: 'https://registry.example.com', + username: 'user', + password: '', + isPublic: true + })) + def('subject', () => $service.createRegistry($registryData, transaction)) + + beforeEach(() => { + RegistryManager.create.resolves(buildRegistryRecord({ id: 17, password: '' })) }) - }) - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { + it('skips password encryption', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(registryCreate) + expect(SecretHelper.encryptSecret).to.not.have.been.called + expect(RegistryManager.update).to.not.have.been.called }) + }) + }) - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => error) + describe('.findRegistries()', () => { + const registries = [buildRegistryRecord()] - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('id') - }) - }) + def('subject', () => $service.findRegistries(isCLI, transaction)) - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls RegistryManager#create() with correct args', async () => { - await $subject - expect(RegistryManager.create).to.have.been.calledWith(registryCreate, transaction) - }) - - context('when RegistryManager#create() fails', () => { - def('createRegistryResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when RegistryManager#create() succeeds', () => { - it('calls ioFogManager#findAll() with correct args', async () => { - await $subject - expect(ioFogManager.findAll).to.have.been.calledWith({ - userId: user.id, - }, transaction) - }) - - context('when ioFogManager#findAll() fails', () => { - def('findIoFogsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findAll() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(ioFogs[0].uuid, - ChangeTrackingService.events.registries, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('findIoFogsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('id') - }) - }) - }) - }) - }) + beforeEach(() => { + $sandbox.stub(RegistryManager, 'findAllWithAttributes').resolves(registries) }) - }) - describe('.findRegistries()', () => { - const transaction = {} - const error = 'Error!' + it('returns registries without password field', async () => { + const result = await $subject + expect(RegistryManager.findAllWithAttributes).to.have.been.calledWith( + {}, + { exclude: ['password'] }, + transaction + ) + expect(result.registries).to.equal(registries) + }) + }) - const user = { - id: 15, - } + describe('.deleteRegistry()', () => { + const registryId = 16 + const registry = buildRegistryRecord({ id: registryId }) - const queryRegistry = isCLI - ? {} - : { - [Op.or]: - [ - { - userId: user.id, - }, - { - isPublic: true, - }, - ], - } - const attributes = { exclude: ['password'] } - def('subject', () => $subject.findRegistries(user, isCLI, transaction)) - def('findRegistriesResponse', () => Promise.resolve([])) + def('subject', () => $service.deleteRegistry({ id: registryId }, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(RegistryManager, 'findAllWithAttributes').returns($findRegistriesResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(RegistryManager, 'findOne').resolves(registry) + $sandbox.stub(MicroserviceManager, 'findAllWithStatuses').resolves([]) + $sandbox.stub(RegistryManager, 'delete').resolves() + stubChangeTrackingDeps($sandbox) }) - it('calls RegistryManager#findAllWithAttributes() with correct args', async () => { + it('deletes an unused registry', async () => { await $subject - expect(RegistryManager.findAllWithAttributes).to.have.been.calledWith(queryRegistry, attributes, transaction) + expect(RegistryManager.delete).to.have.been.calledWith({ id: registryId }, transaction) + expect(ChangeTrackingService.update).to.have.been.called }) - context('when RegistryManager#findAllWithAttributes() fails', () => { - def('findRegistriesResponse', () => Promise.reject(error)) + it('rejects system registry ids', () => { + return expect($service.deleteRegistry({ id: 1 }, isCLI, transaction)) + .to.be.rejectedWith(ErrorMessages.REGISTRY_IS_SYSTEM) + }) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when registry is missing', () => { + beforeEach(() => { + RegistryManager.findOne.resolves(null) }) + + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) - context('when RegistryManager#findAllWithAttributes() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('registries') + context('when registry is in use', () => { + beforeEach(() => { + MicroserviceManager.findAllWithStatuses.resolves([{ uuid: 'msvc-uuid' }]) }) + + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(ErrorMessages.REGISTRY_IS_IN_USE)) }) }) - describe('.deleteRegistry()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const registryData = { - id: 5, - } - - const queryData = isCLI - ? { id: registryData.id } - : { id: registryData.id, userId: user.id } - - const ioFogs = [{ - uuid: 'testUuid', - }] + describe('.updateRegistry()', () => { + const registryId = 16 + const existing = buildRegistryRecord({ id: registryId }) + const updateData = { url: 'https://new-registry.example.com', username: 'new-user' } - def('subject', () => $subject.deleteRegistry(registryData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findRegistryResponse', () => Promise.resolve({ - userId: user.id, - })) - def('deleteRegistryResponse', () => Promise.resolve()) - def('findIoFogsResponse', () => Promise.resolve(ioFogs)) - def('updateChangeTrackingResponse', () => Promise.resolve()) + def('subject', () => $service.updateRegistry(updateData, registryId, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(RegistryManager, 'findOne').returns($findRegistryResponse) - $sandbox.stub(RegistryManager, 'delete').returns($deleteRegistryResponse) - $sandbox.stub(ioFogManager, 'findAll').returns($findIoFogsResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(RegistryManager, 'findOne').resolves(existing) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + $sandbox.stub(RegistryManager, 'update').resolves() + $sandbox.stub(MicroserviceManager, 'findAllWithStatuses').resolves([]) + stubChangeTrackingDeps($sandbox) }) - it('calls Validator#validate() with correct args', async () => { + it('updates registry metadata', async () => { await $subject - expect(Validator.validate).to.have.been.calledWith(registryData, Validator.schemas.registryDelete) + expect(RegistryManager.update).to.have.been.calledWith( + { id: registryId }, + sinon.match({ url: updateData.url, username: updateData.username }), + transaction + ) + expect(ChangeTrackingService.update).to.have.been.calledWith( + 'fog-uuid', + ChangeTrackingService.events.registries, + transaction + ) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('rejects system registry ids', () => { + return expect($service.updateRegistry(updateData, 2, isCLI, transaction)) + .to.be.rejectedWith(ErrorMessages.REGISTRY_IS_SYSTEM) }) - context('when Validator#validate() succeeds', () => { - it('calls RegistryManager#findOne() with correct args', async () => { - await $subject - expect(RegistryManager.findOne).to.have.been.calledWith(queryData, transaction) + context('when registry is missing', () => { + beforeEach(() => { + RegistryManager.findOne.resolves(null) }) - context('when RegistryManager#findOne() fails', () => { - def('findRegistryResponse', () => Promise.reject(error)) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(ErrorMessages.REGISTRY_NOT_FOUND)) + }) + + context('when microservices use the registry', () => { + const microservice = { uuid: 'msvc-uuid', iofogUuid: 'fog-uuid' } - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + beforeEach(() => { + MicroserviceManager.findAllWithStatuses.resolves([microservice]) + $sandbox.stub(MicroserviceManager, 'updateAndFind').resolves(microservice) }) - context('when RegistryManager#findOne() succeeds', () => { - it('calls RegistryManager#delete() with correct args', async () => { - await $subject - expect(RegistryManager.delete).to.have.been.calledWith(queryData, transaction) - }) - - context('when RegistryManager#delete() fails', () => { - def('deleteRegistryResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when RegistryManager#delete() succeeds', () => { - it('calls ioFogManager#findAll() with correct args', async () => { - await $subject - expect(ioFogManager.findAll).to.have.been.calledWith({ - userId: user.id, - }, transaction) - }) - - context('when ioFogManager#findAll() fails', () => { - def('findIoFogsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findAll() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(ioFogs[0].uuid, - ChangeTrackingService.events.registries, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('findIoFogsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) + it('marks microservices for rebuild and updates change tracking', async () => { + await $subject + expect(MicroserviceManager.updateAndFind).to.have.been.calledWith( + { uuid: microservice.uuid }, + { rebuild: true }, + transaction + ) + expect(ChangeTrackingService.update).to.have.been.calledWith( + microservice.iofogUuid, + ChangeTrackingService.events.microserviceCommon, + transaction + ) }) }) - }) - - describe('.updateRegistry()', () => { - const transaction = {} - const error = 'Error!' - const user = { - id: 15, - } + context('when password is cleared and vault reference exists', () => { + beforeEach(() => { + RegistryManager.findOne.resolves({ ...existing, password: 'vault:ref' }) + $sandbox.stub(SecretHelper, 'isVaultReference').returns(true) + $sandbox.stub(SecretHelper, 'deleteSecret').resolves() + }) - const registryId = 5 + def('updateData', () => ({ password: '' })) + def('subject', () => $service.updateRegistry($updateData, registryId, isCLI, transaction)) - const registry = { - url: 'testUrl', - username: 'testUsername', - password: 'testPassword', - isPublic: false, - userEmail: 'testEmail', - requiresCert: false, - certificate: 'testCertificate', - userId: user.id, - } + it('deletes the stored secret', async () => { + await $subject + expect(SecretHelper.deleteSecret).to.have.been.calledWith('registry-16', 'registry') + }) + }) + }) - const registryUpdate = { - url: registry.url, - username: registry.username, - password: registry.password, - isPublic: registry.isPublic, - userEmail: registry.email, - requiresCert: registry.requiresCert, - certificate: registry.certificate, - } + describe('.getRegistry()', () => { + const registryId = 16 + const registry = buildRegistryRecord({ id: registryId }) - const ioFogs = [{ - uuid: 'testUuid', - }] - - const where = isCLI ? - { - id: registryId, - } - : - { - id: registryId, - userId: user.id, - } - - def('subject', () => $subject.updateRegistry(registry, registryId, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findRegistryResponse', () => Promise.resolve({})) - def('deleteUndefinedFieldsResponse', () => registryUpdate) - def('updateRegistryResponse', () => Promise.resolve()) - def('findIoFogsResponse', () => Promise.resolve(ioFogs)) - def('updateChangeTrackingResponse', () => Promise.resolve()) + def('subject', () => $service.getRegistry(registryId, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(RegistryManager, 'findOne').returns($findRegistryResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(RegistryManager, 'update').returns($updateRegistryResponse) - $sandbox.stub(ioFogManager, 'findAll').returns($findIoFogsResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) + $sandbox.stub(RegistryManager, 'findOne').resolves(registry) }) - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(registry, Validator.schemas.registryUpdate) + it('returns the registry record', async () => { + const result = await $subject + expect(RegistryManager.findOne).to.have.been.calledWith({ id: registryId }, transaction) + expect(result).to.equal(registry) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when registry is missing', () => { + beforeEach(() => { + RegistryManager.findOne.resolves(null) }) - }) - context('when Validator#validate() succeeds', () => { - it('calls RegistryManager#findOne() with correct args', async () => { - await $subject - expect(RegistryManager.findOne).to.have.been.calledWith({ - id: registryId, - }, transaction) - }) - - context('when RegistryManager#findOne() fails', () => { - def('findRegistryResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when RegistryManager#findOne() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(registryUpdate) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls RegistryManager#update() with correct args', async () => { - await $subject - expect(RegistryManager.update).to.have.been.calledWith(where, registryUpdate, transaction) - }) - - context('when RegistryManager#update() fails', () => { - def('updateRegistryResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when RegistryManager#update() succeeds', () => { - it('calls ioFogManager#findAll() with correct args', async () => { - await $subject - expect(ioFogManager.findAll).to.have.been.calledWith({ - userId: user.id, - }, transaction) - }) - - context('when ioFogManager#findAll() fails', () => { - def('findIoFogsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findAll() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(ioFogs[0].uuid, - ChangeTrackingService.events.registries, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('findIoFogsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) }) diff --git a/test/src/services/router-service.test.js b/test/src/services/router-service.test.js index 05432a2c..46bb6b7f 100644 --- a/test/src/services/router-service.test.js +++ b/test/src/services/router-service.test.js @@ -10,19 +10,27 @@ const RouterService = require('../../../src/services/router-service') const CatalogService = require('../../../src/services/catalog-service') const Validator = require('../../../src/schemas') const AppHelper = require('../../../src/helpers/app-helper') -const ioFogManager = require('../../../src/data/managers/iofog-manager') const ChangeTrackingService = require('../../../src/services/change-tracking-service') const Sequelize = require('sequelize') const Op = Sequelize.Op const Errors = require('../../../src/helpers/errors') const ErrorMessages = require('../../../src/helpers/error-messages') -const { getProxyCatalogItem } = require('../../../src/services/catalog-service') const constants = require('../../../src/helpers/constants') +const FogManager = require('../../../src/data/managers/iofog-manager') +const SecretManager = require('../../../src/data/managers/secret-manager') +const MicroserviceStatusManager = require('../../../src/data/managers/microservice-status-manager') +const MicroserviceExecStatusManager = require('../../../src/data/managers/microservice-exec-status-manager') +const MicroserviceCapAddManager = require('../../../src/data/managers/microservice-cap-add-manager') +const ApplicationManager = require('../../../src/data/managers/application-manager') +const VolumeMountService = require('../../../src/services/volume-mount-service') +const VolumeMappingManager = require('../../../src/data/managers/volume-mapping-manager') +const MicroservicesService = require('../../../src/services/microservices-service') describe('Router Service', () => { const transaction = {} const randomString = 'randomString' const now = Date.now() + const containerEngine = 'docker' const routerCatalogItem = { id: 5 @@ -30,6 +38,40 @@ describe('Router Service', () => { const userId = 1 + function stubRouterKubernetesDeps (sandbox, fogUuid = 'agentuuid') { + const fog = { uuid: fogUuid, name: 'test-fog' } + sandbox.stub(FogManager, 'findOne').resolves(fog) + sandbox.stub(FogManager, 'findAll').resolves([]) + sandbox.stub(SecretManager, 'getSecret').resolves({ + tlsKey: 'key', + tlsCert: 'cert', + caCert: 'ca' + }) + sandbox.stub(ApplicationManager, 'findOne').callsFake(({ name }) => { + if (name === `system-${fog.name}` || name === `system-${fog.uuid.toLowerCase()}`) { + return Promise.resolve({ id: 99, name, isSystem: true }) + } + return Promise.resolve(null) + }) + sandbox.stub(ApplicationManager, 'create').resolves({ id: 99, name: `system-${fog.name}`, isSystem: true }) + sandbox.stub(ApplicationManager, 'update').resolves() + sandbox.stub(ApplicationManager, 'delete').resolves() + sandbox.stub(MicroservicePortManager, 'delete').resolves() + sandbox.stub(MicroserviceStatusManager, 'create').resolves() + sandbox.stub(MicroserviceExecStatusManager, 'create').resolves() + sandbox.stub(MicroserviceCapAddManager, 'create').resolves() + sandbox.stub(VolumeMountService, 'getVolumeMountEndpoint').rejects({ name: 'NotFoundError' }) + sandbox.stub(VolumeMountService, 'createVolumeMountEndpoint').resolves() + sandbox.stub(VolumeMountService, 'findVolumeMountedFogNodes').resolves([]) + sandbox.stub(VolumeMountService, 'linkVolumeMountEndpoint').resolves() + sandbox.stub(VolumeMappingManager, 'findOne').resolves(null) + sandbox.stub(VolumeMappingManager, 'create').resolves() + sandbox.stub(VolumeMappingManager, 'findAll').resolves([]) + sandbox.stub(MicroservicesService, 'injectServiceAccountVolume').resolves({ created: false }) + sandbox.stub(MicroservicesService, 'createOrUpdateServiceAccountForMicroservice').resolves() + sandbox.stub(ChangeTrackingService, 'update').resolves() + } + def('subject', () => RouterService) def('sandbox', () => sinon.createSandbox()) def('routerCatalogItem', () => routerCatalogItem) @@ -92,11 +134,13 @@ describe('Router Service', () => { } def('fogData', () => fogData) - def('subject', () => $subject.createRouterForFog($fogData, uuid, userId, upstreamRouters, transaction)) + def('subject', () => $subject.createRouterForFog($fogData, uuid, upstreamRouters, transaction)) def('createRouterResponse', () => Promise.resolve(router)) def('createRouterMsvcResponse', () => Promise.resolve(routerMsvc)) beforeEach(() => { + stubRouterKubernetesDeps($sandbox, uuid) + $sandbox.stub(MicroserviceManager, 'findOne').resolves(null) $sandbox.stub(RouterManager, 'create').returns($createRouterResponse) $sandbox.stub(RouterConnectionManager, 'create') $sandbox.stub(MicroservicePortManager, 'create') @@ -120,22 +164,17 @@ describe('Router Service', () => { it('should create a router microservice', async () => { await $subject - expect(MicroserviceManager.create).to.have.been.calledWith({ - uuid: randomString, - name: `Router for Fog ${uuid}`, - config: '{"mode":"edge","id":"agentuuid","listeners":[{"role":"normal","host":"0.0.0.0","port":1234}],"connectors":[{"name":"agentDestUuid","role":"edge","host":"agentDestHost","port":4567}]}', + expect(MicroserviceManager.create).to.have.been.calledOnce + const createArgs = MicroserviceManager.create.firstCall.args[0] + expect(createArgs).to.include({ catalogItemId: routerCatalogItem.id, iofogUuid: uuid, - rootHostAccess: false, - logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId, - configLastUpdated: now - }, transaction) + name: 'router' + }) + expect(createArgs.config).to.be.a('string') const mappingData = { - isPublic: false, portInternal: fogData.messagingPort, portExternal: fogData.messagingPort, - userId: userId, microserviceUuid: routerMsvc.uuid } return expect(MicroservicePortManager.create).to.have.been.calledWith(mappingData, transaction) @@ -145,13 +184,11 @@ describe('Router Service', () => { context('Messaging port not specified', () => { def('fogData', () => ({...fogData, messagingPort: undefined})) - it('Should default to 5672', async () => { - const port = 5672 + it('Should default to 5671', async () => { + const port = 5671 const mappingData = { - isPublic: false, portInternal: port, portExternal: port, - userId: userId, microserviceUuid: routerMsvc.uuid } await $subject @@ -176,8 +213,6 @@ describe('Router Service', () => { it('Should open messaging, edge and inter port', async () => { await $subject const mappingData = { - isPublic: false, - userId: userId, microserviceUuid: routerMsvc.uuid } expect(MicroservicePortManager.create).to.have.been.calledWith({...mappingData, portExternal: interRouterPort, portInternal: interRouterPort}, transaction) @@ -185,21 +220,12 @@ describe('Router Service', () => { return expect(MicroservicePortManager.create).to.have.been.calledThrice }) - it('Should have interior router msvc config', async () => { - await $subject - return expect(MicroserviceManager.create).to.have.been.calledWith({ - uuid: randomString, - name: `Router for Fog ${uuid}`, - config: `{"mode":"interior","id":"${uuid}","listeners":[{"role":"normal","host":"0.0.0.0","port":${$fogData.messagingPort}},{"role":"inter-router","host":"0.0.0.0","port":${$fogData.interRouterPort}},` + - `{"role":"edge","host":"0.0.0.0","port":${$fogData.edgeRouterPort}}],"connectors":[{"name":"agentDestUuid","role":"inter-router","host":"agentDestHost","port":43290}]}`, - catalogItemId: routerCatalogItem.id, - iofogUuid: uuid, - rootHostAccess: false, - logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId, - configLastUpdated: now - }, transaction) - }) + it('Should create an interior router microservice', async () => { + await $subject + expect(MicroserviceManager.create).to.have.been.calledOnce + const createArgs = MicroserviceManager.create.firstCall.args[0] + expect(JSON.parse(createArgs.config).metadata.mode).to.equal('interior') + }) }) }) @@ -212,7 +238,7 @@ describe('Router Service', () => { messagingPort: 5672 } def('routerID', () => routerID) - def('subject', () => $subject.updateConfig($routerID, userId, transaction)) + def('subject', () => $subject.updateConfig($routerID, containerEngine, transaction)) def('router', () => router) def('findOneRouterResponse', () => Promise.resolve($router)) @@ -241,6 +267,7 @@ describe('Router Service', () => { def('msvcFindOneResponse', () => Promise.resolve($routerMsvc)) beforeEach(() => { + stubRouterKubernetesDeps($sandbox, router.iofogUuid) $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) $sandbox.stub(RouterConnectionManager, 'findAllWithRouters').returns($findAllWithRoutersResponse) $sandbox.stub(MicroserviceManager, 'findOne').returns($msvcFindOneResponse) @@ -262,7 +289,7 @@ describe('Router Service', () => { it('Should look for the router msvc', async () => { await $subject - return expect(MicroserviceManager.findOne).to.have.been.calledOnceWith({ + return expect(MicroserviceManager.findOne).to.have.been.calledWith({ catalogItemId: $routerCatalogItem.id, iofogUuid: $router.iofogUuid }, transaction) @@ -345,7 +372,7 @@ describe('Router Service', () => { }}) } - def('subject', () => $subject.updateRouter(oldRouter, newRouterData, upstreamRouters, userId, transaction)) + def('subject', () => $subject.updateRouter(oldRouter, newRouterData, upstreamRouters, containerEngine, transaction)) def('oldRouter', () => oldRouter) def('newRouterData', () => newRouterData) def('upstreamRouters', () => upstreamRouters) @@ -357,16 +384,16 @@ describe('Router Service', () => { let findallWithRoutersStub beforeEach(() => { + stubRouterKubernetesDeps($sandbox, oldRouter.iofogUuid) + $sandbox.stub(MicroservicePortManager, 'create').resolves() const stub = $sandbox.stub(MicroserviceManager, 'findOne').returns($routerMsvcResponse) stub.withArgs({iofogUuid: oldRouter.iofogUuid, catalogItemId: proxyCatalogItem.id}).returns(Promise.resolve(proxyMsvc)) $sandbox.stub(MicroserviceManager, 'update').returns(Promise.resolve()) $sandbox.stub(MicroserviceManager, 'updateIfChanged').returns(Promise.resolve()) $sandbox.stub(RouterManager, 'update') $sandbox.stub(RouterConnectionManager, 'bulkCreate') - $sandbox.stub(CatalogService, 'getProxyCatalogItem').returns(Promise.resolve(proxyCatalogItem)) findallWithRoutersStub = $sandbox.stub(RouterConnectionManager, 'findAllWithRouters') findallWithRoutersStub.returns($findAllWithRoutersResponse) - $sandbox.stub(ChangeTrackingService, 'update') $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) $sandbox.stub(MicroserviceEnvManager, 'delete') $sandbox.stub(MicroserviceEnvManager, 'updateOrCreate') @@ -384,18 +411,6 @@ describe('Router Service', () => { return expect(ChangeTrackingService.update).to.have.been.calledWith($oldRouter.iofogUuid, ChangeTrackingService.events.microserviceList, transaction) }) - it('Should update the proxy', async () => { - await $subject - const newConfig = { - mappings: [], - networkRouter: { - host: newRouterData.host, - port: newRouterData.messagingPort - } - } - return expect(MicroserviceManager.updateIfChanged).to.have.been.calledWith({ uuid: proxyMsvc.uuid }, { config: JSON.stringify(newConfig) }, transaction) - }) - context('Interior to edge', () => { const interRouterPort = 3123123 const edgeRouterPort = 3123 @@ -406,7 +421,6 @@ describe('Router Service', () => { oldRouter.interRouterPort = interRouterPort oldRouter.edgeRouterPort = edgeRouterPort $sandbox.stub(RouterConnectionManager, 'findAll').withArgs({ destRouter: $oldRouter.id }, transaction).returns($downstreamRoutersResponse) - $sandbox.stub(MicroservicePortManager, 'delete') }) afterEach(() => { @@ -417,7 +431,6 @@ describe('Router Service', () => { it('should delete router ports', async () => { await $subject - expect(MicroservicePortManager.delete).to.have.been.calledTwice expect(MicroservicePortManager.delete).to.have.been.calledWith({ microserviceUuid: $routerMsvc.uuid, portInternal: edgeRouterPort }, transaction) expect(MicroservicePortManager.delete).to.have.been.calledWith({ microserviceUuid: $routerMsvc.uuid, portInternal: interRouterPort }, transaction) return expect(RouterManager.update).to.have.been.calledWith({ id: $oldRouter.id }, { ...$newRouterData, interRouterPort: null, edgeRouterPort: null }, transaction) @@ -444,7 +457,6 @@ describe('Router Service', () => { newRouterData.isEdge = false newRouterData.interRouterPort = interRouterPort newRouterData.edgeRouterPort = edgeRouterPort - $sandbox.stub(MicroservicePortManager, 'create') }) afterEach(() => { @@ -455,12 +467,9 @@ describe('Router Service', () => { it('should create router ports', async () => { const mappingData = { - isPublic: false, - userId: userId, microserviceUuid: $routerMsvc.uuid } await $subject - expect(MicroservicePortManager.create).to.have.been.calledTwice expect(MicroservicePortManager.create).to.have.been.calledWith({ ...mappingData, portInternal: edgeRouterPort, portExternal: edgeRouterPort }, transaction) expect(MicroservicePortManager.create).to.have.been.calledWith({ ...mappingData, portInternal: interRouterPort, portExternal: interRouterPort }, transaction) return expect(RouterManager.update).to.have.been.calledWith({ id: $oldRouter.id }, { ...newRouterData }, transaction) @@ -505,7 +514,7 @@ describe('Router Service', () => { edgeRouterPort: 4567, interRouterPort: 43290, }] - def('subject', () => RouterService.updateRouter(oldRouter, newRouterData, upstreamRouters, userId, transaction)) + def('subject', () => RouterService.updateRouter(oldRouter, newRouterData, upstreamRouters, containerEngine, transaction)) it('should create upstream routers connections', async () => { await $subject @@ -669,7 +678,7 @@ describe('Router Service', () => { it('Should update or create router with default port values', async () => { await $subject - return expect(RouterManager.updateOrCreate).to.have.been.calledWith({ isDefault: true }, {...createRouterData, messagingPort: 5672, interRouterPort: 56721, edgeRouterPort: 56722}, transaction) + return expect(RouterManager.updateOrCreate).to.have.been.calledWith({ isDefault: true }, {...createRouterData, messagingPort: 5671, interRouterPort: 55671, edgeRouterPort: 45671}, transaction) }) }) }) @@ -711,7 +720,10 @@ describe('Router Service', () => { }) context('There is a default router', () => { def('subject', () => $subject.validateAndReturnUpstreamRouters(null, true, defaultRouter, transaction)) - it ('Should return an empty array', async () => { + beforeEach(() => { + $sandbox.stub(FogManager, 'findAll').resolves([]) + }) + it ('Should return the default router', async () => { return expect(await $subject).to.eql([defaultRouter]) }) }) @@ -735,6 +747,7 @@ describe('Router Service', () => { beforeEach(() => { $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) + $sandbox.stub(FogManager, 'findOne').resolves(null) }) it('Should return an array with upstreamRouter and defaultRouter', async () => { diff --git a/test/src/services/tunnel-service.test.js b/test/src/services/tunnel-service.test.js index 6f7c1913..f1cdddf2 100644 --- a/test/src/services/tunnel-service.test.js +++ b/test/src/services/tunnel-service.test.js @@ -23,12 +23,11 @@ describe('Tunnel Service', () => { const transaction = {} const error = 'Error!' - def('subject', () => $subject.openTunnel($tunnelData, $user, $cli, transaction)) + def('subject', () => $subject.openTunnel($tunnelData, $cli, transaction)) def('tunnelData', () => ({ iofogUuid: uuid, host: tunnelHost, })) - def('user', () => 'user') def('cli', () => false) def('fog', () => ({ uuid })) def('fogResponse', () => Promise.resolve($fog)) @@ -176,12 +175,11 @@ describe('Tunnel Service', () => { const transaction = {} const error = 'Error!' - def('subject', () => $subject.findTunnel($tunnelData, $user, transaction)) + def('subject', () => $subject.findTunnel($tunnelData, transaction)) def('tunnelData', () => ({ iofogUuid: uuid, host: tunnelHost, })) - def('user', () => 'user') def('tunnelManagerResponse', () => Promise.resolve(tunnel)) beforeEach(() => { @@ -264,12 +262,11 @@ describe('Tunnel Service', () => { const transaction = {} const error = 'Error!' - def('subject', () => $subject.closeTunnel($tunnelData, $user, transaction)) + def('subject', () => $subject.closeTunnel($tunnelData, transaction)) def('tunnelData', () => ({ iofogUuid: uuid, host: tunnelHost, })) - def('user', () => 'user') def('findTunnelResponse', () => Promise.resolve(tunnel)) def('tunnelManagerResponse', () => Promise.resolve()) def('changeResponse', () => Promise.resolve()) @@ -282,7 +279,7 @@ describe('Tunnel Service', () => { it('calls TunnelService#findTunnel() with correct args', async () => { await $subject - expect(TunnelService.findTunnel).to.have.been.calledWith($tunnelData, $user, transaction) + expect(TunnelService.findTunnel).to.have.been.calledWith($tunnelData, transaction) }) context('when TunnelService#findTunnel() fails', () => { diff --git a/test/src/template/template-test.js b/test/src/template/template-test.js index 32893e49..5b63c121 100755 --- a/test/src/template/template-test.js +++ b/test/src/template/template-test.js @@ -33,7 +33,7 @@ describe('rvalues variable substition and scripting', () => { async function subsForFileName(filename, context = {} ) { // Get document, or throw exception on error - let doc = yaml.safeLoad(fs.readFileSync(path.join(__dirname, filename), 'utf8')) + let doc = yaml.load(fs.readFileSync(path.join(__dirname, filename), 'utf8')) Object.assign( context, { self: doc, microservices: [ { iofogUuid: 'edai-smartbuilding-rules-engines' }], 'external-port': $externalPort} ) // console.log('source doc: %j', doc) let response = await rvaluesVarSubstition(doc, context, $user) diff --git a/test/support/oidc-test-helpers.js b/test/support/oidc-test-helpers.js index 148e81d4..fbc5cabe 100644 --- a/test/support/oidc-test-helpers.js +++ b/test/support/oidc-test-helpers.js @@ -4,8 +4,8 @@ const OIDC_ENV_KEYS = [ 'OIDC_ISSUER_URL', 'OIDC_CLIENT_ID', 'OIDC_CLIENT_SECRET', - 'OIDC_VIEWER_CLIENT_ID', - 'AUTH_VIEWER_CLIENT_ENABLED', + 'OIDC_CONSOLE_CLIENT_ID', + 'AUTH_CONSOLE_CLIENT_ENABLED', 'AUTH_SESSION_STORE_TYPE', 'AUTH_SESSION_STORE_TTL_MS', 'AUTH_SESSION_SECRET', From 258487d1fa87a9c4eb26db62975ce31550f54b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 02:17:57 +0300 Subject: [PATCH 60/75] Fix test runner exit behavior and opt-in Kubernetes integration tests. Enable Mocha exit after unit tests, run CLI integration tests separately from npm test, scope coverage to test/src, and move k8s-client tests behind npm run test:k8s-client. --- .mocharc.json | 3 +- scripts/cli-tests.js | 1 + scripts/coverage.js | 2 +- scripts/run-test.js | 45 ++++++++++++------- scripts/test.js | 13 +++--- .../k8s-client-integration.test.js | 13 +++--- 6 files changed, 46 insertions(+), 31 deletions(-) rename test/{src/utils => integration}/k8s-client-integration.test.js (93%) diff --git a/.mocharc.json b/.mocharc.json index 5a5e243b..e694917f 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -1,5 +1,6 @@ { "require": "test/support/setup.js", "ui": "bdd-lazy-var/global", - "recursive": true + "timeout": 15000, + "exit": true } diff --git a/scripts/cli-tests.js b/scripts/cli-tests.js index 26f587c5..f79dc5b4 100644 --- a/scripts/cli-tests.js +++ b/scripts/cli-tests.js @@ -347,6 +347,7 @@ async function cliTest () { process.exit(1) } else { console.log('\nCLI Tests passed successfully.') + process.exit(0) } } diff --git a/scripts/coverage.js b/scripts/coverage.js index bbe48842..1233f5dc 100644 --- a/scripts/coverage.js +++ b/scripts/coverage.js @@ -26,7 +26,7 @@ function coverage () { options.env = setDbEnvVars(options.env) - execSync('nyc mocha', options) + execSync('nyc mocha "test/src/**/*.js"', options) } module.exports = { diff --git a/scripts/run-test.js b/scripts/run-test.js index 817a3a42..90677863 100644 --- a/scripts/run-test.js +++ b/scripts/run-test.js @@ -15,21 +15,34 @@ const { test } = require('./test') const { cliTest } = require('./cli-tests') const { coverage } = require('./coverage') -switch (process.argv[2]) { - case 'test': { - const useReporter = process.argv[3] === 'junit' - const extraArgs = process.argv.slice(useReporter ? 4 : 3).filter(Boolean) - test(useReporter, extraArgs) - cliTest() - break +async function main () { + switch (process.argv[2]) { + case 'test': { + const useReporter = process.argv[3] === 'junit' + const extraArgs = process.argv.slice(useReporter ? 4 : 3).filter(Boolean) + test(useReporter, extraArgs) + break + } + case 'cli-tests': + await cliTest() + break + case 'test-all': { + const useReporter = process.argv[3] === 'junit' + const extraArgs = process.argv.slice(useReporter ? 4 : 3).filter(Boolean) + test(useReporter, extraArgs) + await cliTest() + break + } + case 'coverage': + coverage() + break + default: + console.log('no script for this command') + process.exit(1) } - case 'cli-tests': - cliTest() - break - case 'coverage': - coverage() - break - default: - console.log('no script for this command') - break } + +main().catch((err) => { + console.error(err) + process.exit(1) +}) diff --git a/scripts/test.js b/scripts/test.js index f4a7e75c..86a11386 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -19,7 +19,7 @@ function test (useReporter, extraArgs) { const options = { env: { NODE_ENV: 'test', - VIEWER_PORT: '8008', + CONSOLE_PORT: '8008', PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] @@ -29,12 +29,13 @@ function test (useReporter, extraArgs) { const mochaBin = require.resolve('mocha/bin/mocha.js') const mochaReporterOptions = '--reporter mocha-junit-reporter --reporter-options mochaFile=./unit-results.xml' - let mochaCmd = useReporter ? [mochaBin, mochaReporterOptions].join(' ') : mochaBin - if (extraArgs && extraArgs.length) { - mochaCmd += ' ' + extraArgs.map(a => (a.includes(' ') ? `"${a}"` : a)).join(' ') - execSync(`node "${mochaBin}" ${mochaCmd.slice(mochaBin.length).trim()}`, options) + if (useReporter) { + execSync(`node "${mochaBin}" ${mochaReporterOptions} "test/src/**/*.js"`, options) + } else if (extraArgs && extraArgs.length) { + const args = extraArgs.map(a => (a.includes(' ') ? `"${a}"` : a)).join(' ') + execSync(`node "${mochaBin}" ${args}`, options) } else { - execSync(mochaCmd, options) + execSync(`node "${mochaBin}" "test/src/**/*.js"`, options) } } diff --git a/test/src/utils/k8s-client-integration.test.js b/test/integration/k8s-client-integration.test.js similarity index 93% rename from test/src/utils/k8s-client-integration.test.js rename to test/integration/k8s-client-integration.test.js index f2a94ac4..80d914c3 100644 --- a/test/src/utils/k8s-client-integration.test.js +++ b/test/integration/k8s-client-integration.test.js @@ -1,12 +1,11 @@ /* - * k8s-client integration test. - * Uses namespace "local-test" by default (override with K8S_TEST_NAMESPACE). - * Requires local kubeconfig (KUBECONFIG or ~/.kube/config). - * Run with: nvm use 24 && npm run test:k8s-client - * Skip when no namespace (e.g. K8S_TEST_NAMESPACE="" in CI without a cluster). + * Kubernetes cluster integration tests — not run by `npm test`. + * Requires a live cluster, kubeconfig, and target namespace. + * + * K8S_TEST_NAMESPACE=local-test npm run test:k8s-client */ -const namespace = process.env.K8S_TEST_NAMESPACE || 'local-test' +const namespace = process.env.K8S_TEST_NAMESPACE if (namespace) { process.env.CONTROL_PLANE = 'kubernetes' process.env.CONTROLLER_NAMESPACE = namespace @@ -14,7 +13,7 @@ if (namespace) { } const { expect } = require('chai') -const k8sClient = require('../../../src/utils/k8s-client') +const k8sClient = namespace ? require('../../src/utils/k8s-client') : null const resourcePrefix = 'k8s-client-test-' const testSecretName = resourcePrefix + 'secret' From c0c24770e34054f4f0816a6b8eda1068d85329e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 02:18:03 +0300 Subject: [PATCH 61/75] Restructure CI workflow for split tests, audit, and multi-arch Docker publish. Use npm ci, run unit and CLI tests as separate steps, audit production dependencies, and build images with EdgeOps Console build args. --- .github/workflows/ci.yaml | 255 ++++++++++++++++++-------------------- 1 file changed, 121 insertions(+), 134 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7710bea4..7dd985d8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,156 +1,143 @@ name: CI + on: push: branches: - main - tags: [v*] + - develop + tags: + - v* paths-ignore: - README.md - CHANGELOG.md - LICENSE pull_request: - # Sequence of patterns matched against refs/heads - branches: - - main paths-ignore: - README.md - CHANGELOG.md - LICENSE -env: - IMAGE_NAME: 'controller' - +env: + IMAGE_NAME: controller jobs: - Build: - runs-on: ubuntu-22.04 - permissions: - actions: write - checks: write - contents: write - deployments: write - id-token: write - issues: write - discussions: write - packages: write - pages: write - pull-requests: write - repository-projects: write - security-events: write - statuses: write - name: Preflight + preflight: + name: Lint, test, audit + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 24 - - name: Replace values - shell: bash - env: - PAT: ${{ secrets.PAT }} - run: | - sed -i.back "s|PAT|${PAT}|g" .npmrc - - run: npm config set @datasance:registry https://npm.pkg.github.com/ - - run: npm install --build-from-source --force - - run: npm run standard - - run: | - npm i -g better-npm-audit - npx better-npm-audit audit -p - - Publish: - needs: [Build] + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + + - name: Install dependencies + run: npm ci --legacy-peer-deps + + - name: Lint + run: npm run standard + + - name: Unit test + run: npm test + + - name: Audit production dependencies + run: npx --yes better-npm-audit audit -p + + docker: + name: Docker build + needs: preflight runs-on: ubuntu-22.04 permissions: - actions: write - checks: write - contents: write - deployments: write - id-token: write - issues: write - discussions: write + contents: read packages: write - pages: write - pull-requests: write - repository-projects: write - security-events: write - statuses: write - name: Publish Controller steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 24 - - name: Replace values - shell: bash - env: - PAT: ${{ secrets.PAT }} - run: | - sed -i.back "s|PAT|${PAT}|g" .npmrc - - run: npm config set @datasance:registry https://npm.pkg.github.com/ - - run: npm install --build-from-source --force - - - name: npm version - id: package-version - uses: martinbeentjes/npm-get-version-action@v1.3.1 - - - name: package version - shell: bash - id: version - run: | - if [[ ${{ github.ref_name }} =~ ^v.* ]] ; then - echo "pkg_version=${{ steps.package-version.outputs.current-version}}" >> "${GITHUB_OUTPUT}" - else - echo "pkg_version=${{ steps.package-version.outputs.current-version}}-${{ github.run_number }}" >> "${GITHUB_OUTPUT}" - fi - - - name: npm pack with version from package version - run: | - npm pack - npm publish --registry=https://npm.pkg.github.com/ - - - name: 'Get Previous tag' - id: previoustag - uses: "WyriHaximus/github-action-get-previous-tag@v1" - with: - fallback: 0.0.0 - - name: Set image tag - shell: bash - id: tags - run: | - if [[ ${{ github.ref_name }} =~ ^v.* ]] ; then - VERSION=${{ github.ref_name }} - echo "VERSION=${VERSION:1}" >> "${GITHUB_OUTPUT}" - else - VERSION=${{ steps.previoustag.outputs.tag }} - echo "VERSION=${VERSION:1}-${{ github.run_number }}" >> "${GITHUB_OUTPUT}" - fi - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - version: v0.29.1 - - - - name: Login to Github Container Registry - uses: docker/login-action@v2 - with: - registry: "ghcr.io" - username: ${{ github.actor }} - password: ${{ secrets.PAT }} - - - name: Build and Push to ghcr - uses: docker/build-push-action@v3 - id: build_push_ghcr - with: - file: Dockerfile - context: . - platforms: linux/amd64, linux/arm64 - push: true - outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=Controller - build-args: GITHUB_TOKEN=${{ secrets.PAT }} - tags: | - ghcr.io/datasance/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.VERSION }} - ghcr.io/datasance/${{ env.IMAGE_NAME }}:latest - ghcr.io/datasance/${{ env.IMAGE_NAME }}:main + - uses: actions/checkout@v4 + + - name: Package version + id: package-version + uses: martinbeentjes/npm-get-version-action@v1.3.1 + + - name: Set build versions + id: version + shell: bash + run: | + PKG="${{ steps.package-version.outputs.current-version }}" + if [[ "${{ github.ref_name }}" =~ ^v.* ]]; then + echo "pkg_version=${PKG}" >> "${GITHUB_OUTPUT}" + echo "image_tag=${PKG}" >> "${GITHUB_OUTPUT}" + else + echo "pkg_version=${PKG}-${{ github.run_number }}" >> "${GITHUB_OUTPUT}" + echo "image_tag=${PKG}-${{ github.run_number }}" >> "${GITHUB_OUTPUT}" + fi + + - name: Set EdgeOps Console build args + shell: bash + run: | + VERSION="${{ vars.EDGEOPS_CONSOLE_VERSION }}" + if [ -z "$VERSION" ]; then VERSION="1.0.0"; fi + echo "EDGEOPS_CONSOLE_VERSION=$VERSION" >> "${GITHUB_ENV}" + + REPO="${{ vars.EDGEOPS_CONSOLE_REPO }}" + FLAVOR="${{ vars.EDGEOPS_CONSOLE_FLAVOR }}" + if [ -z "$REPO" ] || [ -z "$FLAVOR" ]; then + case "${{ github.repository }}" in + eclipse-iofog/Controller|eclipse-iofog/controller) + REPO="${REPO:-https://github.com/eclipse-iofog/edgeops-console}" + FLAVOR="${FLAVOR:-iofog}" + ;; + *) + REPO="${REPO:-https://github.com/Datasance/edgeops-console}" + FLAVOR="${FLAVOR:-datasance}" + ;; + esac + fi + echo "EDGEOPS_CONSOLE_REPO=$REPO" >> "${GITHUB_ENV}" + echo "EDGEOPS_CONSOLE_FLAVOR=$FLAVOR" >> "${GITHUB_ENV}" + + - name: Set image registry + shell: bash + run: | + REGISTRY="${{ vars.IMAGE_REGISTRY }}" + if [ -z "$REGISTRY" ]; then + case "${{ github.repository }}" in + eclipse-iofog/Controller|eclipse-iofog/controller) + REGISTRY="ghcr.io/eclipse-iofog" + ;; + *) + REGISTRY="ghcr.io/datasance" + ;; + esac + fi + echo "IMAGE_REGISTRY=$REGISTRY" >> "${GITHUB_ENV}" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: | + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.image_tag }} + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:main + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:latest + build-args: | + PKG_VERSION=${{ steps.version.outputs.pkg_version }} + EDGEOPS_CONSOLE_REPO=${{ env.EDGEOPS_CONSOLE_REPO }} + EDGEOPS_CONSOLE_VERSION=${{ env.EDGEOPS_CONSOLE_VERSION }} + EDGEOPS_CONSOLE_FLAVOR=${{ env.EDGEOPS_CONSOLE_FLAVOR }} From ad01d89782156ce807bb69dc4cbdcca15fb6f587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 03:34:13 +0300 Subject: [PATCH 62/75] Add build-flavor configuration for distribution-specific product strings. Introduce flavor helpers for RBAC apiVersion, service annotation tags, and Kubernetes component labels. Rename the npm package to controller, default namespace to iofog, and point system image defaults at ghcr.io/eclipse-iofog. --- package-lock.json | 8 +++---- package.json | 14 ++++--------- src/config/config.yaml | 33 +++++++++++++++++------------ src/config/env-mapping.js | 4 ++++ src/config/flavor.js | 34 ++++++++++++++++++++++++++++++ src/config/rbac-system-roles.js | 34 +++++++++++++----------------- src/services/iofog-service.js | 20 ++++-------------- src/services/services-service.js | 36 +++++++++++++------------------- 8 files changed, 99 insertions(+), 84 deletions(-) create mode 100644 src/config/flavor.js diff --git a/package-lock.json b/package-lock.json index e1b905c2..33a861a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "@datasance/iofogcontroller", - "version": "3.7.4", + "name": "controller", + "version": "3.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@datasance/iofogcontroller", - "version": "3.7.4", + "name": "controller", + "version": "3.8.0", "hasInstallScript": true, "license": "EPL-2.0", "dependencies": { diff --git a/package.json b/package.json index b24321e4..93d7dbe9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "@datasance/iofogcontroller", - "version": "3.7.4", - "description": "ioFog Controller project for Datasance PoT @ datasance.com \\nCopyright (c) 2023 Datasance Teknoloji A.S.", + "name": "controller", + "version": "3.8.0", + "description": "A cloud-native operations controller for managing edge computing workloads, nodes, and deployments across Eclipse IoFog and Datasance PoT.", "main": "./src/main.js", "author": "Emirhan Durmus", "contributors": [ @@ -12,9 +12,6 @@ "engines": { "node": "^24.0.0" }, - "bugs": { - "email": "support@datasance.com" - }, "standard": { "ignore": [ "test/**/*.js", @@ -25,10 +22,7 @@ "homepage": "https://www.datasance.com", "repository": { "type": "git", - "url": "https://github.com/Datasance/Controller" - }, - "publishConfig": { - "registry": "https://npm.pkg.github.com/" + "url": "https://github.com/eclipse-iofog/Controller" }, "scripts": { "prestart": "npm run lint", diff --git a/src/config/config.yaml b/src/config/config.yaml index ddb8d966..8991daa5 100644 --- a/src/config/config.yaml +++ b/src/config/config.yaml @@ -3,7 +3,7 @@ app: name: pot # Application name uuid: "" controlPlane: Remote # Control plane type: Remote or Kubernetes or Local - namespace: datasance # Namespace for the application + namespace: iofog # Namespace for the application (PoT: CONTROLLER_NAMESPACE=datasance) # Server Configuration server: @@ -125,6 +125,13 @@ auth: # sessionTtlSeconds: # AUTH_OIDC_SESSION_TTL_SECONDS — defaults to AuthPolicy refresh_token_ttl_seconds # idTokenTtlSeconds: # AUTH_OIDC_ID_TOKEN_TTL_SECONDS — defaults to AuthPolicy access_token_ttl_seconds +# Build flavor (Plan 12 — override via env or Docker build-arg) +flavor: + distribution: datasance + rbacApiVersion: datasance.com/v3 + serviceAnnotationTag: service.iofog.org/tag + componentLabelDomain: iofog.org/component + # Bridge Ports Configuration for Services bridgePorts: range: "10024-65535" # Bridge ports range @@ -132,20 +139,20 @@ bridgePorts: # System Images Configuration systemImages: router: - "1": "ghcr.io/datasance/router:latest" - "2": "ghcr.io/datasance/router:latest" - "3": "ghcr.io/datasance/router:latest" - "4": "ghcr.io/datasance/router:latest" + "1": "ghcr.io/eclipse-iofog/router:latest" + "2": "ghcr.io/eclipse-iofog/router:latest" + "3": "ghcr.io/eclipse-iofog/router:latest" + "4": "ghcr.io/eclipse-iofog/router:latest" debug: - "1": "ghcr.io/datasance/node-debugger:latest" - "2": "ghcr.io/datasance/node-debugger:latest" - "3": "ghcr.io/datasance/node-debugger:latest" - "4": "ghcr.io/datasance/node-debugger:latest" + "1": "ghcr.io/eclipse-iofog/node-debugger:latest" + "2": "ghcr.io/eclipse-iofog/node-debugger:latest" + "3": "ghcr.io/eclipse-iofog/node-debugger:latest" + "4": "ghcr.io/eclipse-iofog/node-debugger:latest" nats: - "1": "ghcr.io/datasance/nats:latest" - "2": "ghcr.io/datasance/nats:latest" - "3": "ghcr.io/datasance/nats:latest" - "4": "ghcr.io/datasance/nats:latest" + "1": "ghcr.io/eclipse-iofog/nats:latest" + "2": "ghcr.io/eclipse-iofog/nats:latest" + "3": "ghcr.io/eclipse-iofog/nats:latest" + "4": "ghcr.io/eclipse-iofog/nats:latest" # NATS Configuration nats: diff --git a/src/config/env-mapping.js b/src/config/env-mapping.js index 568c4c35..ae94ac59 100644 --- a/src/config/env-mapping.js +++ b/src/config/env-mapping.js @@ -4,6 +4,10 @@ module.exports = { CONTROLLER_UUID: 'app.uuid', CONTROL_PLANE: 'app.controlPlane', CONTROLLER_NAMESPACE: 'app.namespace', + CONTROLLER_DISTRIBUTION: 'flavor.distribution', + RBAC_API_VERSION: 'flavor.rbacApiVersion', + SERVICE_ANNOTATION_TAG: 'flavor.serviceAnnotationTag', + COMPONENT_LABEL_DOMAIN: 'flavor.componentLabelDomain', // Server Configuration SERVER_PORT: 'server.port', diff --git a/src/config/flavor.js b/src/config/flavor.js new file mode 100644 index 00000000..163edb26 --- /dev/null +++ b/src/config/flavor.js @@ -0,0 +1,34 @@ +const config = require('./index') + +const DEFAULT_RBAC_API_VERSION = 'datasance.com/v3' +const DEFAULT_CONTROLLER_DISTRIBUTION = 'datasance' +const DEFAULT_SERVICE_ANNOTATION_TAG = 'service.iofog.org/tag' +const DEFAULT_COMPONENT_LABEL_DOMAIN = 'iofog.org/component' +const DEFAULT_APP_LABEL = 'iofog' + +function getRbacApiVersion () { + return process.env.RBAC_API_VERSION || config.get('flavor.rbacApiVersion', DEFAULT_RBAC_API_VERSION) +} + +function getControllerDistribution () { + return process.env.CONTROLLER_DISTRIBUTION || config.get('flavor.distribution', DEFAULT_CONTROLLER_DISTRIBUTION) +} + +function getServiceAnnotationTag () { + return process.env.SERVICE_ANNOTATION_TAG || config.get('flavor.serviceAnnotationTag', DEFAULT_SERVICE_ANNOTATION_TAG) +} + +function getComponentLabelKey () { + return process.env.COMPONENT_LABEL_DOMAIN || config.get('flavor.componentLabelDomain', DEFAULT_COMPONENT_LABEL_DOMAIN) +} + +function getAppLabelKey () { + return process.env.APP_LABEL || config.get('flavor.defaultAppLabelKey', DEFAULT_APP_LABEL) +} + +module.exports = { + getRbacApiVersion, + getControllerDistribution, + getServiceAnnotationTag, + getComponentLabelKey +} diff --git a/src/config/rbac-system-roles.js b/src/config/rbac-system-roles.js index 8861f7f7..e9051fea 100644 --- a/src/config/rbac-system-roles.js +++ b/src/config/rbac-system-roles.js @@ -1,31 +1,21 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - /** * Hardcoded system roles configuration * Admin role is fixed and cannot be modified, created, or deleted - * Note: Namespace is set from controller config at runtime, 'datasance' is default + * Note: Namespace is set from controller config at runtime, 'iofog' is default */ const config = require('./index') +const { getRbacApiVersion } = require('./flavor') function getNamespace () { - return process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'datasance') + return process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'iofog') } module.exports = { ADMIN_ROLE: { name: 'admin', - apiVersion: 'datasance.com/v3', + get apiVersion () { + return getRbacApiVersion() + }, kind: 'Role', get namespace () { return getNamespace() @@ -40,7 +30,9 @@ module.exports = { }, SRE_ROLE: { name: 'sre', - apiVersion: 'datasance.com/v3', + get apiVersion () { + return getRbacApiVersion() + }, kind: 'Role', get namespace () { return getNamespace() @@ -60,7 +52,9 @@ module.exports = { }, DEVELOPER_ROLE: { name: 'developer', - apiVersion: 'datasance.com/v3', + get apiVersion () { + return getRbacApiVersion() + }, kind: 'Role', get namespace () { return getNamespace() @@ -80,7 +74,9 @@ module.exports = { }, VIEWER_ROLE: { name: 'viewer', - apiVersion: 'datasance.com/v3', + get apiVersion () { + return getRbacApiVersion() + }, kind: 'Role', get namespace () { return getNamespace() diff --git a/src/services/iofog-service.js b/src/services/iofog-service.js index 64749ac9..02ae548f 100644 --- a/src/services/iofog-service.js +++ b/src/services/iofog-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const config = require('../config') const fs = require('fs') const TransactionDecorator = require('../decorators/transaction-decorator') @@ -63,12 +50,12 @@ const SecretManager = require('../data/managers/secret-manager') const vaultManager = require('../vault/vault-manager') const SecretHelper = require('../helpers/secret-helper') const FogPublicKeyManager = require('../data/managers/iofog-public-key-manager') +const { getServiceAnnotationTag } = require('../config/flavor') const SITE_CA_CERT = Constants.ROUTER_SITE_CA const DEFAULT_ROUTER_LOCAL_CA = Constants.DEFAULT_ROUTER_LOCAL_CA const NATS_SITE_CA = Constants.NATS_SITE_CA const DEFAULT_NATS_LOCAL_CA = Constants.DEFAULT_NATS_LOCAL_CA -const SERVICE_ANNOTATION_TAG = 'service.datasance.com/tag' const _fogToken = (fog) => slugifyName((fog && fog.name) || (fog && fog.uuid) || 'fog') @@ -1028,9 +1015,10 @@ async function _extractServiceTags (fogTags) { return [] } - // Filter tags that start with SERVICE_ANNOTATION_TAG + // Filter tags that start with the service annotation key + const serviceAnnotationTag = getServiceAnnotationTag() const serviceTags = fogTags - .filter(tag => tag.startsWith(SERVICE_ANNOTATION_TAG)) + .filter(tag => tag.startsWith(serviceAnnotationTag)) .map(tag => { // Extract the value after the colon const parts = tag.split(':') diff --git a/src/services/services-service.js b/src/services/services-service.js index 6581d7e6..3c8a14ec 100644 --- a/src/services/services-service.js +++ b/src/services/services-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TransactionDecorator = require('../decorators/transaction-decorator') const ServiceManager = require('../data/managers/service-manager') const MicroserviceManager = require('../data/managers/microservice-manager') @@ -31,10 +18,10 @@ const { ensureSystemApplication, getSystemMicroserviceName } = require('../helpers/system-naming') +const { getServiceAnnotationTag, getComponentLabelKey } = require('../config/flavor') // const { Op } = require('sequelize') const K8S_ROUTER_CONFIG_MAP = 'iofog-router' -const SERVICE_ANNOTATION_TAG = 'service.datasance.com/tag' const EDGELET_BRIDGE_CONNECTOR_HOST = 'edgelet.default.bridge.local' // Map service tags to string array @@ -60,10 +47,11 @@ async function _setTags (serviceModel, tagsArray, transaction) { async function handleServiceDistribution (serviceTags, transaction) { const tags = Array.isArray(serviceTags) ? serviceTags : (serviceTags ? [].concat(serviceTags) : []) logger.debug('handleServiceDistribution: entry', { serviceTagsType: typeof serviceTags, isArray: Array.isArray(serviceTags), tagsLength: tags.length }) + const serviceAnnotationTag = getServiceAnnotationTag() // Always find fog nodes with 'all' tag const allTaggedFogNodesRaw = await FogManager.findAllWithTags({ - '$tags.value$': `${SERVICE_ANNOTATION_TAG}: all` + '$tags.value$': `${serviceAnnotationTag}: all` }, transaction) const allTaggedFogNodes = Array.isArray(allTaggedFogNodesRaw) ? allTaggedFogNodesRaw : [] logger.debug('handleServiceDistribution: allTaggedFogNodes', { length: allTaggedFogNodes.length }) @@ -92,7 +80,7 @@ async function handleServiceDistribution (serviceTags, transaction) { const specificTaggedFogNodes = new Set() for (const tag of filteredServiceTags) { const fogNodesRaw = await FogManager.findAllWithTags({ - '$tags.value$': `${SERVICE_ANNOTATION_TAG}: ${tag}` + '$tags.value$': `${serviceAnnotationTag}: ${tag}` }, transaction) const fogNodes = Array.isArray(fogNodesRaw) ? fogNodesRaw : [] fogNodes.forEach(fog => specificTaggedFogNodes.add(fog.uuid)) @@ -855,11 +843,13 @@ async function _deleteTcpListener (serviceName, transaction) { // Common labels for Kubernetes services created by the controller function _getK8sServiceLabels () { + const componentLabelKey = getComponentLabelKey() + const appLabelKey = getAppLabelKey() return { - 'app.kubernetes.io/name': 'pot', + 'app.kubernetes.io/name': appLabelKey, 'app.kubernetes.io/component': 'controller', 'app.kubernetes.io/managed-by': 'controller', - 'datasance.com/component': 'router', + [componentLabelKey]: 'router', 'app.kubernetes.io/instance': process.env.CONTROLLER_NAME || config.get('app.name') } } @@ -867,6 +857,7 @@ function _getK8sServiceLabels () { // Helper function to create Kubernetes service async function _createK8sService (serviceConfig, transaction) { const normalizedTags = serviceConfig.tags.map(tag => tag.includes(':') ? tag : `${tag}:`) + const componentLabelKey = getComponentLabelKey() const serviceSpec = { apiVersion: 'v1', kind: 'Service', @@ -882,10 +873,10 @@ async function _createK8sService (serviceConfig, transaction) { spec: { type: serviceConfig.k8sType, selector: { - 'datasance.com/component': 'router' + [componentLabelKey]: 'router' }, ports: [{ - name: 'pot-service', + name: 'iofog-service', targetPort: parseInt(serviceConfig.bridgePort), port: parseInt(serviceConfig.servicePort), protocol: 'TCP' @@ -919,6 +910,7 @@ async function _updateK8sService (serviceConfig, transaction) { return service } else { const normalizedTags = serviceConfig.tags.map(tag => tag.includes(':') ? tag : `${tag}:`) + const componentLabelKey = getComponentLabelKey() const patchData = { metadata: { labels: _getK8sServiceLabels(), @@ -931,10 +923,10 @@ async function _updateK8sService (serviceConfig, transaction) { spec: { type: serviceConfig.k8sType, selector: { - 'datasance.com/component': 'router' + [componentLabelKey]: 'router' }, ports: [{ - name: 'pot-service', + name: 'iofog-service', port: parseInt(serviceConfig.servicePort), targetPort: parseInt(serviceConfig.bridgePort), protocol: 'TCP' From efbabde190110cb1a783cd255530f4a77fc3b8a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 03:34:17 +0300 Subject: [PATCH 63/75] Parameterize Docker OCI labels and restrict image publish to version tags. Pass IMAGE_REGISTRY, OCI_SOURCE_REPO, and flavor build-args through CI and the Dockerfile. Push container images only on v* tags; PR and branch builds still run lint, tests, and docker build without push. --- .github/workflows/ci.yaml | 40 +++++++++++++++++++++++++++++++++------ Dockerfile | 16 +++++++++++----- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7dd985d8..2f1d5a11 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -95,21 +95,45 @@ jobs: echo "EDGEOPS_CONSOLE_REPO=$REPO" >> "${GITHUB_ENV}" echo "EDGEOPS_CONSOLE_FLAVOR=$FLAVOR" >> "${GITHUB_ENV}" - - name: Set image registry + - name: Set image registry and OCI source shell: bash run: | REGISTRY="${{ vars.IMAGE_REGISTRY }}" - if [ -z "$REGISTRY" ]; then + OCI_SOURCE="${{ vars.OCI_SOURCE_REPO }}" + if [ -z "$REGISTRY" ] || [ -z "$OCI_SOURCE" ]; then case "${{ github.repository }}" in eclipse-iofog/Controller|eclipse-iofog/controller) - REGISTRY="ghcr.io/eclipse-iofog" + REGISTRY="${REGISTRY:-ghcr.io/eclipse-iofog}" + OCI_SOURCE="${OCI_SOURCE:-https://github.com/eclipse-iofog/Controller}" ;; *) - REGISTRY="ghcr.io/datasance" + REGISTRY="${REGISTRY:-ghcr.io/datasance}" + OCI_SOURCE="${OCI_SOURCE:-https://github.com/Datasance/Controller}" ;; esac fi echo "IMAGE_REGISTRY=$REGISTRY" >> "${GITHUB_ENV}" + echo "OCI_SOURCE_REPO=$OCI_SOURCE" >> "${GITHUB_ENV}" + + - name: Set controller flavor build args + shell: bash + run: | + DISTRIBUTION="${{ vars.CONTROLLER_DISTRIBUTION }}" + RBAC_API="${{ vars.RBAC_API_VERSION }}" + if [ -z "$DISTRIBUTION" ] || [ -z "$RBAC_API" ]; then + case "${{ github.repository }}" in + eclipse-iofog/Controller|eclipse-iofog/controller) + DISTRIBUTION="${DISTRIBUTION:-iofog}" + RBAC_API="${RBAC_API:-iofog.org/v3}" + ;; + *) + DISTRIBUTION="${DISTRIBUTION:-datasance}" + RBAC_API="${RBAC_API:-datasance.com/v3}" + ;; + esac + fi + echo "CONTROLLER_DISTRIBUTION=$DISTRIBUTION" >> "${GITHUB_ENV}" + echo "RBAC_API_VERSION=$RBAC_API" >> "${GITHUB_ENV}" - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -118,7 +142,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Log in to GitHub Container Registry - if: github.event_name != 'pull_request' + if: startsWith(github.ref, 'refs/tags/v') uses: docker/login-action@v3 with: registry: ghcr.io @@ -131,7 +155,7 @@ jobs: context: . file: Dockerfile platforms: linux/amd64,linux/arm64 - push: ${{ github.event_name != 'pull_request' }} + push: ${{ startsWith(github.ref, 'refs/tags/v') }} tags: | ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.image_tag }} ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:main @@ -141,3 +165,7 @@ jobs: EDGEOPS_CONSOLE_REPO=${{ env.EDGEOPS_CONSOLE_REPO }} EDGEOPS_CONSOLE_VERSION=${{ env.EDGEOPS_CONSOLE_VERSION }} EDGEOPS_CONSOLE_FLAVOR=${{ env.EDGEOPS_CONSOLE_FLAVOR }} + IMAGE_REGISTRY=${{ env.IMAGE_REGISTRY }} + OCI_SOURCE_REPO=${{ env.OCI_SOURCE_REPO }} + CONTROLLER_DISTRIBUTION=${{ env.CONTROLLER_DISTRIBUTION }} + RBAC_API_VERSION=${{ env.RBAC_API_VERSION }} diff --git a/Dockerfile b/Dockerfile index 58193f48..81d56b75 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,7 +30,6 @@ RUN test -f build/index.html \ FROM node:24-bookworm AS builder ARG PKG_VERSION -# ARG GITHUB_TOKEN WORKDIR /tmp @@ -50,6 +49,10 @@ RUN npm pack FROM registry.access.redhat.com/ubi9/nodejs-24-minimal:latest ARG EDGEOPS_CONSOLE_VERSION=1.0.0 +ARG IMAGE_REGISTRY +ARG OCI_SOURCE_REPO +ARG CONTROLLER_DISTRIBUTION=datasance +ARG RBAC_API_VERSION=datasance.com/v3 USER root # Install dependencies for logging and development @@ -80,20 +83,23 @@ ENV NPM_CONFIG_PREFIX=/home/runner/.npm-global ENV NPM_CONFIG_CACHE=/home/runner/.npm ENV PATH=$PATH:/home/runner/.npm-global/bin -COPY --from=builder /tmp/datasance-iofogcontroller-*.tgz /home/runner/iofog-controller.tgz +COPY --from=builder /tmp/controller-*.tgz /home/runner/iofog-controller.tgz ENV PID_BASE=/home/runner ENV EDGEOPS_CONSOLE_PATH=/home/runner/static/console ENV EDGEOPS_CONSOLE_VERSION=${EDGEOPS_CONSOLE_VERSION} +ENV CONTROLLER_DISTRIBUTION=${CONTROLLER_DISTRIBUTION} +ENV RBAC_API_VERSION=${RBAC_API_VERSION} RUN npm i -g /home/runner/iofog-controller.tgz && \ rm -rf /home/runner/iofog-controller.tgz && \ iofog-controller config dev-mode --on -RUN rm -rf /home/runner/.npm-global/lib/node_modules/@datasance/iofogcontroller/src/data/sqlite_files/* +RUN rm -rf /home/runner/.npm-global/lib/node_modules/controller/src/data/sqlite_files/* COPY LICENSE /licenses/LICENSE LABEL org.opencontainers.image.description=controller -LABEL org.opencontainers.image.source=https://github.com/datasance/controller +LABEL org.opencontainers.image.source=${OCI_SOURCE_REPO} LABEL org.opencontainers.image.licenses=EPL2.0 -CMD [ "node", "/home/runner/.npm-global/lib/node_modules/@datasance/iofogcontroller/src/server.js" ] +LABEL org.opencontainers.image.url=${IMAGE_REGISTRY}/controller +CMD [ "node", "/home/runner/.npm-global/lib/node_modules/controller/src/server.js" ] From 34b46f663ae50390c5b84e81944be36eb4af2057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 03:34:21 +0300 Subject: [PATCH 64/75] Remove per-file copyright headers; NOTICE is the sole attribution. Strip EPL header blocks from source files and drop embedded copyright from CLI help text. Add the Eclipse ioFog contributor copyright line to NOTICE. --- NOTICE | 2 + src/cli/application.js | 13 ------ src/cli/base-cli-handler.js | 17 +------ src/cli/catalog.js | 13 ------ src/cli/cli-data-types.js | 13 ------ src/cli/config.js | 13 ------ src/cli/controller.js | 13 ------ src/cli/index.js | 13 ------ src/cli/iofog.js | 13 ------ src/cli/microservice.js | 13 ------ src/cli/registry.js | 13 ------ src/cli/start.js | 13 ------ src/cli/tunnel.js | 13 ------ src/config/index.js | 13 ------ src/config/nats-system-rules.js | 13 ------ src/config/telemetry.js | 2 +- src/controllers/agent-controller.js | 13 ------ src/controllers/application-controller.js | 13 ------ .../application-template-controller.js | 13 ------ src/controllers/catalog-controller.js | 13 ------ src/controllers/cluster-controller.js | 13 ------ src/controllers/config-controller.js | 13 ------ src/controllers/config-map-controller.js | 13 ------ src/controllers/controller.js | 13 ------ src/controllers/event-controller.js | 13 ------ src/controllers/iofog-controller.js | 13 ------ src/controllers/microservices-controller.js | 13 ------ src/controllers/nats-controller.js | 13 ------ src/controllers/rbac-controller.js | 13 ------ src/controllers/registry-controller.js | 13 ------ src/controllers/router-controller.js | 13 ------ src/controllers/secret-controller.js | 13 ------ src/controllers/service-controller.js | 13 ------ src/controllers/tunnel-controller.js | 13 ------ src/controllers/user-controller.js | 13 ------ src/controllers/users-controller.js | 13 ------ src/controllers/volume-mount-controller.js | 13 ------ src/daemon.js | 13 ------ src/data/managers/application-manager.js | 13 ------ .../managers/application-template-manager.js | 13 ------ .../application-template-variable-manager.js | 13 ------ src/data/managers/architecture-manager.js | 13 ------ src/data/managers/base-manager.js | 13 ------ .../managers/catalog-item-image-manager.js | 13 ------ .../catalog-item-input-type-manager.js | 13 ------ src/data/managers/catalog-item-manager.js | 13 ------ .../catalog-item-output-type-manager.js | 13 ------ src/data/managers/change-tracking-manager.js | 13 ------ .../managers/cluster-controller-manager.js | 13 ------ src/data/managers/config-manager.js | 13 ------ src/data/managers/event-manager.js | 13 ------ src/data/managers/fog-log-status-manager.js | 13 ------ src/data/managers/fog-used-token-manager.js | 13 ------ src/data/managers/hw-info-manager.js | 13 ------ src/data/managers/iofog-manager.js | 13 ------ .../managers/iofog-provision-key-manager.js | 13 ------ src/data/managers/iofog-public-key-manager.js | 13 ------ .../managers/iofog-version-command-manager.js | 13 ------ src/data/managers/microservice-arg-manager.js | 13 ------ .../managers/microservice-cap-add-manager.js | 13 ------ .../managers/microservice-cap-drop-manager.js | 13 ------ .../microservice-cdi-device-manager.js | 13 ------ src/data/managers/microservice-env-manager.js | 13 ------ .../microservice-exec-status-manager.js | 13 ------ .../microservice-extra-host-manager.js | 13 ------ .../microservice-healthcheck-manager.js | 13 ------ .../microservice-log-status-manager.js | 13 ------ src/data/managers/microservice-manager.js | 13 ------ .../managers/microservice-port-manager.js | 13 ------ .../managers/microservice-status-manager.js | 13 ------ .../managers/rbac-cache-version-manager.js | 13 ------ .../managers/rbac-role-binding-manager.js | 13 ------ src/data/managers/rbac-role-manager.js | 13 ------ .../managers/rbac-service-account-manager.js | 13 ------ .../managers/router-connection-manager.js | 13 ------ src/data/managers/router-manager.js | 13 ------ src/data/managers/service-manager.js | 13 ------ src/data/managers/tags-manager.js | 13 ------ src/data/managers/usb-info-manager.js | 13 ------ src/data/managers/volume-mapping-manager.js | 13 ------ src/data/managers/volume-mounting-manager.js | 13 ------ src/data/models/changetracking.js | 13 ------ .../seeders/mysql/db_seeder_mysql_v1.0.2.sql | 26 +++++------ .../seeders/mysql/db_seeder_mysql_v3.8.0.sql | 46 +++++++++---------- .../seeders/postgres/db_seeder_pg_v1.0.2.sql | 26 +++++------ .../seeders/postgres/db_seeder_pg_v3.8.0.sql | 46 +++++++++---------- .../sqlite/db_seeder_sqlite_v1.0.2.sql | 26 +++++------ .../sqlite/db_seeder_sqlite_v3.8.0.sql | 46 +++++++++---------- src/decorators/authorization-decorator.js | 12 ----- src/decorators/response-decorator.js | 12 ----- src/decorators/transaction-decorator.js | 12 ----- src/enums/fog-state.js | 13 ------ src/enums/microservice-state.js | 13 ------ src/helpers/app-helper.js | 13 ------ src/helpers/cert-dns-sans.js | 13 ------ src/helpers/constants.js | 13 ------ src/helpers/error-messages.js | 13 ------ src/helpers/errors.js | 13 ------ src/helpers/system-naming.js | 13 ------ src/helpers/template-helper.js | 13 ------ src/init.js | 13 ------ src/jobs/controller-cleanup-job.js | 13 ------ src/jobs/controller-heartbeat-job.js | 13 ------ src/jobs/event-cleanup-job.js | 13 ------ src/jobs/fog-status-job.js | 13 ------ src/jobs/fog-token-cleanup-job.js | 13 ------ src/jobs/nats-reconcile-worker-job.js | 13 ------ src/jobs/stopped-app-status-job.js | 13 ------ src/keycloak.json | 14 ------ src/lib/rbac/authorizer.js | 13 ------ src/lib/rbac/middleware.js | 13 ------ src/logger/index.js | 13 ------ src/main.js | 13 ------ src/middlewares/auth-rate-limit-middleware.js | 13 ------ src/middlewares/event-audit-middleware.js | 13 ------ src/routes/agent.js | 13 ------ src/routes/application.js | 12 ----- src/routes/applicationTemplate.js | 12 ----- src/routes/auth.js | 13 ------ src/routes/capabilities.js | 12 ----- src/routes/catalog.js | 12 ----- src/routes/cluster.js | 12 ----- src/routes/config.js | 12 ----- src/routes/configMap.js | 13 ------ src/routes/controller.js | 12 ----- src/routes/event.js | 13 ------ src/routes/iofog.js | 12 ----- src/routes/microservices.js | 12 ----- src/routes/nats.js | 12 ----- src/routes/rbac.js | 13 ------ src/routes/registries.js | 12 ----- src/routes/router.js | 12 ----- src/routes/secret.js | 13 ------ src/routes/service.js | 13 ------ src/routes/tunnel.js | 12 ----- src/routes/user.js | 12 ----- src/routes/users.js | 13 ------ src/routes/volumeMount.js | 12 ----- src/schemas/agent.js | 13 ------ src/schemas/catalog.js | 13 ------ src/schemas/cluster-controller.js | 13 ------ src/schemas/config.js | 13 ------ src/schemas/controlPlane.js | 13 ------ src/schemas/controller-register.js | 13 ------ src/schemas/index.js | 13 ------ src/schemas/iofog.js | 13 ------ src/schemas/rbac.js | 13 ------ src/schemas/registry.js | 13 ------ src/schemas/tunnel.js | 13 ------ src/schemas/user.js | 13 ------ src/server.js | 13 ------ src/services/agent-service.js | 13 ------ src/services/application-service.js | 13 ------ src/services/application-template-service.js | 13 ------ src/services/catalog-service.js | 13 ------ src/services/change-tracking-service.js | 12 ----- src/services/cluster-controller-service.js | 13 ------ src/services/config-map-service.js | 13 ------ src/services/config-service.js | 13 ------ src/services/controller-ms-service.js | 13 ------ src/services/controller-service.js | 13 ------ src/services/event-service.js | 13 ------ src/services/iofog-key-service.js | 13 ------ .../microservice-ports/microservice-port.js | 13 ------ src/services/microservices-service.js | 13 ------ src/services/nats-api-service.js | 13 ------ src/services/nats-auth-service.js | 13 ------ src/services/nats-hub-service.js | 13 ------ src/services/nats-service.js | 13 ------ src/services/rbac-service.js | 13 ------ src/services/registry-service.js | 13 ------ src/services/router-service.js | 17 +------ src/services/secret-service.js | 13 ------ src/services/tunnel-service.js | 13 ------ src/services/user-service.js | 13 ------ src/utils/ssl-utils.js | 13 ------ src/vault/aws-secrets-manager-provider.js | 17 +------ src/vault/azure-key-vault-provider.js | 15 +----- src/vault/base-vault-provider.js | 13 ------ src/vault/google-secret-manager-provider.js | 15 +----- src/vault/hashicorp-vault-provider.js | 15 +----- src/vault/vault-manager.js | 15 +----- src/websocket/log-session-manager.js | 13 ------ 183 files changed, 121 insertions(+), 2376 deletions(-) delete mode 100644 src/keycloak.json diff --git a/NOTICE b/NOTICE index 286d69a9..9e4e4911 100644 --- a/NOTICE +++ b/NOTICE @@ -10,6 +10,8 @@ Eclipse ioFog is a trademark of the Eclipse Foundation. ## Copyright +Copyright (c) 2023 Contributors to the Eclipse ioFog Project + All content is the property of the respective authors or their employers. For more information regarding authorship of content, please consult the listed source code repository logs. diff --git a/src/cli/application.js b/src/cli/application.js index e5997dd8..687bf241 100644 --- a/src/cli/application.js +++ b/src/cli/application.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const ApplicationService = require('../services/application-service') diff --git a/src/cli/base-cli-handler.js b/src/cli/base-cli-handler.js index da2adb3a..3b0e7a28 100644 --- a/src/cli/base-cli-handler.js +++ b/src/cli/base-cli-handler.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const commandLineArgs = require('command-line-args') const commandLineUsage = require('command-line-usage') const AppHelper = require('../helpers/app-helper') @@ -62,7 +49,7 @@ class CLIHandler { const usage = [ { header: 'ioFogController', - content: 'Fog Controller project for Eclipse IoFog @ iofog.org \\nCopyright (c) 2023 Datasance Teknoloji A.S.' + content: 'Fog Controller project for Eclipse ioFog @ iofog.org' } ].concat(sections) logger.cliRes(commandLineUsage(usage)) @@ -95,7 +82,7 @@ class CLIHandler { const usage = [ { header: 'ioFogController', - content: 'Fog Controller project for Eclipse IoFog @ iofog.org \\nCopyright (c) 2023 Datasance Teknoloji A.S.' + content: 'Fog Controller project for Eclipse ioFog @ iofog.org' } ].concat(sections) logger.cliRes(commandLineUsage(usage)) diff --git a/src/cli/catalog.js b/src/cli/catalog.js index dc2473f4..9604a540 100644 --- a/src/cli/catalog.js +++ b/src/cli/catalog.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const logger = require('../logger') diff --git a/src/cli/cli-data-types.js b/src/cli/cli-data-types.js index fa1aabd4..bab29a46 100644 --- a/src/cli/cli-data-types.js +++ b/src/cli/cli-data-types.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - function Integer (value) { return Number(value) } diff --git a/src/cli/config.js b/src/cli/config.js index e6c7bcdd..59ea0c01 100644 --- a/src/cli/config.js +++ b/src/cli/config.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const config = require('../config') const constants = require('../helpers/constants') diff --git a/src/cli/controller.js b/src/cli/controller.js index 7e4e003b..90b92496 100644 --- a/src/cli/controller.js +++ b/src/cli/controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const ControllerService = require('../services/controller-service') diff --git a/src/cli/index.js b/src/cli/index.js index 97f3596b..0ec624b1 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const Start = require('./start') const Config = require('./config') diff --git a/src/cli/iofog.js b/src/cli/iofog.js index ece5a2e8..aa13141d 100644 --- a/src/cli/iofog.js +++ b/src/cli/iofog.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const logger = require('../logger') diff --git a/src/cli/microservice.js b/src/cli/microservice.js index a82d42e1..1c36e073 100644 --- a/src/cli/microservice.js +++ b/src/cli/microservice.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const ErrorMessages = require('../helpers/error-messages') diff --git a/src/cli/registry.js b/src/cli/registry.js index 1efee349..faded1a6 100644 --- a/src/cli/registry.js +++ b/src/cli/registry.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const logger = require('../logger') diff --git a/src/cli/start.js b/src/cli/start.js index 68649332..0dbe787b 100644 --- a/src/cli/start.js +++ b/src/cli/start.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const config = require('../config') diff --git a/src/cli/tunnel.js b/src/cli/tunnel.js index 75efe9ff..1d79d890 100644 --- a/src/cli/tunnel.js +++ b/src/cli/tunnel.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const fs = require('fs') diff --git a/src/config/index.js b/src/config/index.js index a2325273..bd292048 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const nconf = require('nconf') const path = require('path') const fs = require('fs') diff --git a/src/config/nats-system-rules.js b/src/config/nats-system-rules.js index 8bcd63e0..f7ef9403 100644 --- a/src/config/nats-system-rules.js +++ b/src/config/nats-system-rules.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - 'use strict' const SYSTEM_ACCOUNT_RULE_NAME = 'default-system-account' diff --git a/src/config/telemetry.js b/src/config/telemetry.js index dd969184..9ecd47e9 100644 --- a/src/config/telemetry.js +++ b/src/config/telemetry.js @@ -25,7 +25,7 @@ function awaitAttributes (detector) { // Initialize OpenTelemetry const sdk = new NodeSDK({ - serviceName: process.env.OTEL_SERVICE_NAME || 'pot-controller', + serviceName: process.env.OTEL_SERVICE_NAME || 'iofog-controller', resource: new Resource({}), resourceDetectors: [ awaitAttributes(envDetectorSync), diff --git a/src/controllers/agent-controller.js b/src/controllers/agent-controller.js index fa9b199c..5822f4c7 100644 --- a/src/controllers/agent-controller.js +++ b/src/controllers/agent-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const AgentService = require('../services/agent-service') const ControllerMsService = require('../services/controller-ms-service') const AuthDecorator = require('../decorators/authorization-decorator') diff --git a/src/controllers/application-controller.js b/src/controllers/application-controller.js index 89597b46..3fdb83c4 100644 --- a/src/controllers/application-controller.js +++ b/src/controllers/application-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ApplicationService = require('../services/application-service') const YAMLParserService = require('../services/yaml-parser-service') const errors = require('../helpers/errors') diff --git a/src/controllers/application-template-controller.js b/src/controllers/application-template-controller.js index b7e8f192..e1d67008 100644 --- a/src/controllers/application-template-controller.js +++ b/src/controllers/application-template-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ApplicationTemplateService = require('../services/application-template-service') const YAMLParserService = require('../services/yaml-parser-service') const errors = require('../helpers/errors') diff --git a/src/controllers/catalog-controller.js b/src/controllers/catalog-controller.js index d04e7451..2c1515f9 100644 --- a/src/controllers/catalog-controller.js +++ b/src/controllers/catalog-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const CatalogService = require('../services/catalog-service') const createCatalogItemEndPoint = async function (req) { diff --git a/src/controllers/cluster-controller.js b/src/controllers/cluster-controller.js index e937d11b..75277634 100644 --- a/src/controllers/cluster-controller.js +++ b/src/controllers/cluster-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ClusterControllerService = require('../services/cluster-controller-service') const listClusterControllersEndPoint = async function (req) { diff --git a/src/controllers/config-controller.js b/src/controllers/config-controller.js index 29892201..692510ae 100644 --- a/src/controllers/config-controller.js +++ b/src/controllers/config-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ConfigService = require('../services/config-service') const upsertConfigElementEndpoint = async function (req) { diff --git a/src/controllers/config-map-controller.js b/src/controllers/config-map-controller.js index 3a0a4e63..c5d182f0 100644 --- a/src/controllers/config-map-controller.js +++ b/src/controllers/config-map-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ConfigMapService = require('../services/config-map-service') const YamlParserService = require('../services/yaml-parser-service') diff --git a/src/controllers/controller.js b/src/controllers/controller.js index 48390539..367a9714 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ControllerService = require('../services/controller-service') const statusControllerEndPoint = async function (req) { diff --git a/src/controllers/event-controller.js b/src/controllers/event-controller.js index 62f49107..d39acd01 100644 --- a/src/controllers/event-controller.js +++ b/src/controllers/event-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const EventService = require('../services/event-service') /** diff --git a/src/controllers/iofog-controller.js b/src/controllers/iofog-controller.js index c24de528..13d62f5e 100644 --- a/src/controllers/iofog-controller.js +++ b/src/controllers/iofog-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const FogService = require('../services/iofog-service') const qs = require('qs') diff --git a/src/controllers/microservices-controller.js b/src/controllers/microservices-controller.js index 781326ed..770e54f1 100644 --- a/src/controllers/microservices-controller.js +++ b/src/controllers/microservices-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const MicroservicesService = require('../services/microservices-service') const YAMLParserService = require('../services/yaml-parser-service') const { rvaluesVarSubstition } = require('../helpers/template-helper') diff --git a/src/controllers/nats-controller.js b/src/controllers/nats-controller.js index 3b55605f..241a5d99 100644 --- a/src/controllers/nats-controller.js +++ b/src/controllers/nats-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const NatsApiService = require('../services/nats-api-service') const YamlParserService = require('../services/yaml-parser-service') diff --git a/src/controllers/rbac-controller.js b/src/controllers/rbac-controller.js index d10e76b7..d825dc2a 100644 --- a/src/controllers/rbac-controller.js +++ b/src/controllers/rbac-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const RbacService = require('../services/rbac-service') const YamlParserService = require('../services/yaml-parser-service') diff --git a/src/controllers/registry-controller.js b/src/controllers/registry-controller.js index 19292fd5..ce594c14 100644 --- a/src/controllers/registry-controller.js +++ b/src/controllers/registry-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const RegistryService = require('../services/registry-service') const createRegistryEndPoint = async function (req) { diff --git a/src/controllers/router-controller.js b/src/controllers/router-controller.js index 8f718766..0369b68c 100644 --- a/src/controllers/router-controller.js +++ b/src/controllers/router-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const RouterService = require('../services/router-service') const upsertDefaultRouter = async function (req) { diff --git a/src/controllers/secret-controller.js b/src/controllers/secret-controller.js index 826bf34d..5d35b739 100644 --- a/src/controllers/secret-controller.js +++ b/src/controllers/secret-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const SecretService = require('../services/secret-service') const YamlParserService = require('../services/yaml-parser-service') diff --git a/src/controllers/service-controller.js b/src/controllers/service-controller.js index e47bf68a..41bc8ca3 100644 --- a/src/controllers/service-controller.js +++ b/src/controllers/service-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ServiceService = require('../services/services-service') const YamlParserService = require('../services/yaml-parser-service') diff --git a/src/controllers/tunnel-controller.js b/src/controllers/tunnel-controller.js index 4cf7dad8..80e126f4 100644 --- a/src/controllers/tunnel-controller.js +++ b/src/controllers/tunnel-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TunnelService = require('../services/tunnel-service') const Errors = require('../helpers/errors') const ErrorMessages = require('../helpers/error-messages') diff --git a/src/controllers/user-controller.js b/src/controllers/user-controller.js index d4a79482..e9bf8759 100644 --- a/src/controllers/user-controller.js +++ b/src/controllers/user-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const UserService = require('../services/user-service') const Validator = require('../schemas') diff --git a/src/controllers/users-controller.js b/src/controllers/users-controller.js index 234ee902..072b518a 100644 --- a/src/controllers/users-controller.js +++ b/src/controllers/users-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const AuthUserService = require('../services/auth-user-service') const Validator = require('../schemas') diff --git a/src/controllers/volume-mount-controller.js b/src/controllers/volume-mount-controller.js index 9628a8ba..6b9ec174 100644 --- a/src/controllers/volume-mount-controller.js +++ b/src/controllers/volume-mount-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const VolumeMountService = require('../services/volume-mount-service') const YAMLParserService = require('../services/yaml-parser-service') diff --git a/src/daemon.js b/src/daemon.js index 5813bf08..595f05c3 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const daemonize = require('daemonize2') const logger = require('./logger') const path = require('path') diff --git a/src/data/managers/application-manager.js b/src/data/managers/application-manager.js index f16d3d08..38c9d7cc 100644 --- a/src/data/managers/application-manager.js +++ b/src/data/managers/application-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Application = models.Application diff --git a/src/data/managers/application-template-manager.js b/src/data/managers/application-template-manager.js index 2eb6a23c..63bf58ce 100644 --- a/src/data/managers/application-template-manager.js +++ b/src/data/managers/application-template-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const ApplicationTemplate = models.ApplicationTemplate diff --git a/src/data/managers/application-template-variable-manager.js b/src/data/managers/application-template-variable-manager.js index 66fae8f2..547af2d7 100644 --- a/src/data/managers/application-template-variable-manager.js +++ b/src/data/managers/application-template-variable-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const ApplicationTemplateVariable = models.ApplicationTemplateVariable diff --git a/src/data/managers/architecture-manager.js b/src/data/managers/architecture-manager.js index 7611d1f0..f64045c7 100644 --- a/src/data/managers/architecture-manager.js +++ b/src/data/managers/architecture-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Architecture = models.Architecture diff --git a/src/data/managers/base-manager.js b/src/data/managers/base-manager.js index 5f960ac8..f7e7f325 100644 --- a/src/data/managers/base-manager.js +++ b/src/data/managers/base-manager.js @@ -1,16 +1,3 @@ -/* -* ******************************************************************************* -* * Copyright (c) 2023 Datasance Teknoloji A.S. -* * -* * This program and the accompanying materials are made available under the -* * terms of the Eclipse Public License v. 2.0 which is available at -* * http://www.eclipse.org/legal/epl-2.0 -* * -* * SPDX-License-Identifier: EPL-2.0 -* ******************************************************************************* -* -*/ - const AppHelper = require('../../helpers/app-helper') const Errors = require('../../helpers/errors') diff --git a/src/data/managers/catalog-item-image-manager.js b/src/data/managers/catalog-item-image-manager.js index 14076f1c..8db26ef5 100644 --- a/src/data/managers/catalog-item-image-manager.js +++ b/src/data/managers/catalog-item-image-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const CatalogItemImage = models.CatalogItemImage diff --git a/src/data/managers/catalog-item-input-type-manager.js b/src/data/managers/catalog-item-input-type-manager.js index 2aa19d81..1cc568e1 100644 --- a/src/data/managers/catalog-item-input-type-manager.js +++ b/src/data/managers/catalog-item-input-type-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const CatalogItemInputType = models.CatalogItemInputType diff --git a/src/data/managers/catalog-item-manager.js b/src/data/managers/catalog-item-manager.js index 90a48d36..9b9d9f1f 100644 --- a/src/data/managers/catalog-item-manager.js +++ b/src/data/managers/catalog-item-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const CatalogItem = models.CatalogItem diff --git a/src/data/managers/catalog-item-output-type-manager.js b/src/data/managers/catalog-item-output-type-manager.js index 6ec69012..cbc7185f 100644 --- a/src/data/managers/catalog-item-output-type-manager.js +++ b/src/data/managers/catalog-item-output-type-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const CatalogItemOutputType = models.CatalogItemOutputType diff --git a/src/data/managers/change-tracking-manager.js b/src/data/managers/change-tracking-manager.js index 1f74c4c9..4350f1b1 100644 --- a/src/data/managers/change-tracking-manager.js +++ b/src/data/managers/change-tracking-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const ChangeTracking = models.ChangeTracking diff --git a/src/data/managers/cluster-controller-manager.js b/src/data/managers/cluster-controller-manager.js index 7b7d767c..e2db0a5f 100644 --- a/src/data/managers/cluster-controller-manager.js +++ b/src/data/managers/cluster-controller-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const ClusterController = require('../models').ClusterController diff --git a/src/data/managers/config-manager.js b/src/data/managers/config-manager.js index 824f2340..6a275df2 100644 --- a/src/data/managers/config-manager.js +++ b/src/data/managers/config-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Config = models.Config diff --git a/src/data/managers/event-manager.js b/src/data/managers/event-manager.js index f23d4f33..302c0b9c 100644 --- a/src/data/managers/event-manager.js +++ b/src/data/managers/event-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Event = models.Event diff --git a/src/data/managers/fog-log-status-manager.js b/src/data/managers/fog-log-status-manager.js index 34f3c28f..c337a22f 100644 --- a/src/data/managers/fog-log-status-manager.js +++ b/src/data/managers/fog-log-status-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const FogLogStatus = models.FogLogStatus diff --git a/src/data/managers/fog-used-token-manager.js b/src/data/managers/fog-used-token-manager.js index 15328213..aa534227 100644 --- a/src/data/managers/fog-used-token-manager.js +++ b/src/data/managers/fog-used-token-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const models = require('../models') const logger = require('../../logger') const { Op } = require('sequelize') diff --git a/src/data/managers/hw-info-manager.js b/src/data/managers/hw-info-manager.js index 9a8fcadb..c65e89a3 100644 --- a/src/data/managers/hw-info-manager.js +++ b/src/data/managers/hw-info-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const HWInfo = models.HWInfo diff --git a/src/data/managers/iofog-manager.js b/src/data/managers/iofog-manager.js index a4f8c437..988a0a78 100644 --- a/src/data/managers/iofog-manager.js +++ b/src/data/managers/iofog-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') diff --git a/src/data/managers/iofog-provision-key-manager.js b/src/data/managers/iofog-provision-key-manager.js index 0e6e1ca4..040b0f4a 100644 --- a/src/data/managers/iofog-provision-key-manager.js +++ b/src/data/managers/iofog-provision-key-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const FogProvisionKey = models.FogProvisionKey diff --git a/src/data/managers/iofog-public-key-manager.js b/src/data/managers/iofog-public-key-manager.js index 71b99983..7e88a80e 100644 --- a/src/data/managers/iofog-public-key-manager.js +++ b/src/data/managers/iofog-public-key-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const FogPublicKey = models.FogPublicKey diff --git a/src/data/managers/iofog-version-command-manager.js b/src/data/managers/iofog-version-command-manager.js index db027a5b..a6f11cf7 100644 --- a/src/data/managers/iofog-version-command-manager.js +++ b/src/data/managers/iofog-version-command-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const FogVersionCommand = models.FogVersionCommand diff --git a/src/data/managers/microservice-arg-manager.js b/src/data/managers/microservice-arg-manager.js index 5ccf6343..120b0493 100644 --- a/src/data/managers/microservice-arg-manager.js +++ b/src/data/managers/microservice-arg-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceArg = models.MicroserviceArg diff --git a/src/data/managers/microservice-cap-add-manager.js b/src/data/managers/microservice-cap-add-manager.js index b92430fa..091ee59b 100644 --- a/src/data/managers/microservice-cap-add-manager.js +++ b/src/data/managers/microservice-cap-add-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceCapAdd = models.MicroserviceCapAdd diff --git a/src/data/managers/microservice-cap-drop-manager.js b/src/data/managers/microservice-cap-drop-manager.js index 2dce889f..6fc30a9b 100644 --- a/src/data/managers/microservice-cap-drop-manager.js +++ b/src/data/managers/microservice-cap-drop-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceCapDrop = models.MicroserviceCapDrop diff --git a/src/data/managers/microservice-cdi-device-manager.js b/src/data/managers/microservice-cdi-device-manager.js index dc13ab60..90b600a1 100644 --- a/src/data/managers/microservice-cdi-device-manager.js +++ b/src/data/managers/microservice-cdi-device-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceCdiDev = models.MicroserviceCdiDev diff --git a/src/data/managers/microservice-env-manager.js b/src/data/managers/microservice-env-manager.js index a9ffae34..f5c17f6b 100644 --- a/src/data/managers/microservice-env-manager.js +++ b/src/data/managers/microservice-env-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceEnv = models.MicroserviceEnv diff --git a/src/data/managers/microservice-exec-status-manager.js b/src/data/managers/microservice-exec-status-manager.js index ff5edec7..cb49837b 100644 --- a/src/data/managers/microservice-exec-status-manager.js +++ b/src/data/managers/microservice-exec-status-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceExecStatus = models.MicroserviceExecStatus diff --git a/src/data/managers/microservice-extra-host-manager.js b/src/data/managers/microservice-extra-host-manager.js index 22e0a340..a1438ea8 100644 --- a/src/data/managers/microservice-extra-host-manager.js +++ b/src/data/managers/microservice-extra-host-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Errors = require('../../helpers/errors') diff --git a/src/data/managers/microservice-healthcheck-manager.js b/src/data/managers/microservice-healthcheck-manager.js index 9d905bcd..149ca87f 100644 --- a/src/data/managers/microservice-healthcheck-manager.js +++ b/src/data/managers/microservice-healthcheck-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceHealthCheck = models.MicroserviceHealthCheck diff --git a/src/data/managers/microservice-log-status-manager.js b/src/data/managers/microservice-log-status-manager.js index deaa3ff6..4b4fe883 100644 --- a/src/data/managers/microservice-log-status-manager.js +++ b/src/data/managers/microservice-log-status-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceLogStatus = models.MicroserviceLogStatus diff --git a/src/data/managers/microservice-manager.js b/src/data/managers/microservice-manager.js index 302b6a43..2039e6fd 100644 --- a/src/data/managers/microservice-manager.js +++ b/src/data/managers/microservice-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Microservice = models.Microservice diff --git a/src/data/managers/microservice-port-manager.js b/src/data/managers/microservice-port-manager.js index 84c7c22f..777013b7 100644 --- a/src/data/managers/microservice-port-manager.js +++ b/src/data/managers/microservice-port-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroservicePort = models.MicroservicePort diff --git a/src/data/managers/microservice-status-manager.js b/src/data/managers/microservice-status-manager.js index 9690e21d..d0241026 100644 --- a/src/data/managers/microservice-status-manager.js +++ b/src/data/managers/microservice-status-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceStatus = models.MicroserviceStatus diff --git a/src/data/managers/rbac-cache-version-manager.js b/src/data/managers/rbac-cache-version-manager.js index dca1b5a8..3ad6013e 100644 --- a/src/data/managers/rbac-cache-version-manager.js +++ b/src/data/managers/rbac-cache-version-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const RbacCacheVersion = models.RbacCacheVersion diff --git a/src/data/managers/rbac-role-binding-manager.js b/src/data/managers/rbac-role-binding-manager.js index ce1a323a..72deaebd 100644 --- a/src/data/managers/rbac-role-binding-manager.js +++ b/src/data/managers/rbac-role-binding-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const RbacRoleBinding = models.RbacRoleBinding diff --git a/src/data/managers/rbac-role-manager.js b/src/data/managers/rbac-role-manager.js index 25490c8c..69b16e47 100644 --- a/src/data/managers/rbac-role-manager.js +++ b/src/data/managers/rbac-role-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const RbacRole = models.RbacRole diff --git a/src/data/managers/rbac-service-account-manager.js b/src/data/managers/rbac-service-account-manager.js index b7a57d1f..5adc6475 100644 --- a/src/data/managers/rbac-service-account-manager.js +++ b/src/data/managers/rbac-service-account-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const RbacServiceAccount = models.RbacServiceAccount diff --git a/src/data/managers/router-connection-manager.js b/src/data/managers/router-connection-manager.js index 4acf323c..c6d9ae84 100644 --- a/src/data/managers/router-connection-manager.js +++ b/src/data/managers/router-connection-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const RouterConnection = models.RouterConnection diff --git a/src/data/managers/router-manager.js b/src/data/managers/router-manager.js index 645d7b4d..aa72a9c9 100644 --- a/src/data/managers/router-manager.js +++ b/src/data/managers/router-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Router = models.Router diff --git a/src/data/managers/service-manager.js b/src/data/managers/service-manager.js index a5ff00a1..21ad1176 100644 --- a/src/data/managers/service-manager.js +++ b/src/data/managers/service-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Service = models.Service diff --git a/src/data/managers/tags-manager.js b/src/data/managers/tags-manager.js index ebde21a1..118c6494 100644 --- a/src/data/managers/tags-manager.js +++ b/src/data/managers/tags-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Tags = models.Tags diff --git a/src/data/managers/usb-info-manager.js b/src/data/managers/usb-info-manager.js index 85f3a423..a3e81871 100644 --- a/src/data/managers/usb-info-manager.js +++ b/src/data/managers/usb-info-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const USBInfo = models.USBInfo diff --git a/src/data/managers/volume-mapping-manager.js b/src/data/managers/volume-mapping-manager.js index 0f4df9ac..7cc1d8bb 100644 --- a/src/data/managers/volume-mapping-manager.js +++ b/src/data/managers/volume-mapping-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const VolumeMapping = models.VolumeMapping diff --git a/src/data/managers/volume-mounting-manager.js b/src/data/managers/volume-mounting-manager.js index dfe73e81..8c1b5487 100644 --- a/src/data/managers/volume-mounting-manager.js +++ b/src/data/managers/volume-mounting-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const VolumeMount = models.VolumeMount diff --git a/src/data/models/changetracking.js b/src/data/models/changetracking.js index 710d17d4..e131dfc4 100644 --- a/src/data/models/changetracking.js +++ b/src/data/models/changetracking.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - 'use strict' module.exports = (sequelize, DataTypes) => { const ChangeTracking = sequelize.define('ChangeTracking', { diff --git a/src/data/seeders/mysql/db_seeder_mysql_v1.0.2.sql b/src/data/seeders/mysql/db_seeder_mysql_v1.0.2.sql index d20573e2..14e7e987 100644 --- a/src/data/seeders/mysql/db_seeder_mysql_v1.0.2.sql +++ b/src/data/seeders/mysql/db_seeder_mysql_v1.0.2.sql @@ -7,11 +7,11 @@ VALUES INSERT INTO `CatalogItems` (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) VALUES - ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('Router', 'The built-in router for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('Debug', 'The built-in debugger for Datasance PoT IoFog Agent.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + ('Debug', 'The built-in debugger for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Edgelet', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); INSERT INTO `FogTypes` (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) VALUES @@ -25,16 +25,16 @@ WHERE fog_type_id IS NULL; INSERT INTO `CatalogItemImages` (catalog_item_id, fog_type_id, container_image) VALUES - (1, 1, 'ghcr.io/datasance/router:latest'), - (1, 2, 'ghcr.io/datasance/router:latest'), - (2, 1, 'ghcr.io/datasance/restblue:latest'), - (2, 2, 'ghcr.io/datasance/restblue:latest'), - (3, 1, 'ghcr.io/datasance/hal:latest'), - (3, 2, 'ghcr.io/datasance/hal:latest'), - (4, 1, 'ghcr.io/datasance/node-debugger:latest'), - (4, 2, 'ghcr.io/datasance/node-debugger:latest'), - (5, 1, 'ghcr.io/datasance/nats:latest'), - (5, 2, 'ghcr.io/datasance/nats:latest'); + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'); COMMIT; \ No newline at end of file diff --git a/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql b/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql index 71ab9656..34d942b9 100644 --- a/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql +++ b/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql @@ -7,11 +7,11 @@ VALUES INSERT INTO `CatalogItems` (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) VALUES - ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('Router', 'The built-in router for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('Debug', 'The built-in debugger for Datasance PoT Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + ('Debug', 'The built-in debugger for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Edgelet', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); INSERT INTO `Architectures` (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) VALUES @@ -27,26 +27,26 @@ WHERE arch_id IS NULL; INSERT INTO `CatalogItemImages` (catalog_item_id, arch_id, container_image) VALUES - (1, 1, 'ghcr.io/datasance/router:latest'), - (1, 2, 'ghcr.io/datasance/router:latest'), - (1, 3, 'ghcr.io/datasance/router:latest'), - (1, 4, 'ghcr.io/datasance/router:latest'), - (2, 1, 'ghcr.io/datasance/restblue:latest'), - (2, 2, 'ghcr.io/datasance/restblue:latest'), - (2, 3, 'ghcr.io/datasance/restblue:latest'), - (2, 4, 'ghcr.io/datasance/restblue:latest'), - (3, 1, 'ghcr.io/datasance/hal:latest'), - (3, 2, 'ghcr.io/datasance/hal:latest'), - (3, 3, 'ghcr.io/datasance/hal:latest'), - (3, 4, 'ghcr.io/datasance/hal:latest'), - (4, 1, 'ghcr.io/datasance/node-debugger:latest'), - (4, 2, 'ghcr.io/datasance/node-debugger:latest'), - (4, 3, 'ghcr.io/datasance/node-debugger:latest'), - (4, 4, 'ghcr.io/datasance/node-debugger:latest'), - (5, 1, 'ghcr.io/datasance/nats:latest'), - (5, 2, 'ghcr.io/datasance/nats:latest'), - (5, 3, 'ghcr.io/datasance/nats:latest'), - (5, 4, 'ghcr.io/datasance/nats:latest'); + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 3, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 4, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 3, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 4, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 3, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 4, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 3, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 4, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 3, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 4, 'ghcr.io/eclipse-iofog/nats:latest'); INSERT IGNORE INTO AuthPolicy ( id, diff --git a/src/data/seeders/postgres/db_seeder_pg_v1.0.2.sql b/src/data/seeders/postgres/db_seeder_pg_v1.0.2.sql index 8efec984..0799cd5d 100644 --- a/src/data/seeders/postgres/db_seeder_pg_v1.0.2.sql +++ b/src/data/seeders/postgres/db_seeder_pg_v1.0.2.sql @@ -7,11 +7,11 @@ VALUES INSERT INTO "CatalogItems" (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) VALUES - ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('Router', 'The built-in router for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('Debug', 'The built-in debugger for Datasance PoT IoFog Agent.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + ('Debug', 'The built-in debugger for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Edgelet', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); INSERT INTO "FogTypes" (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) VALUES @@ -25,15 +25,15 @@ WHERE fog_type_id IS NULL; INSERT INTO "CatalogItemImages" (catalog_item_id, fog_type_id, container_image) VALUES - (1, 1, 'ghcr.io/datasance/router:latest'), - (1, 2, 'ghcr.io/datasance/router:latest'), - (2, 1, 'ghcr.io/datasance/restblue:latest'), - (2, 2, 'ghcr.io/datasance/restblue:latest'), - (3, 1, 'ghcr.io/datasance/hal:latest'), - (3, 2, 'ghcr.io/datasance/hal:latest'), - (4, 1, 'ghcr.io/datasance/node-debugger:latest'), - (4, 2, 'ghcr.io/datasance/node-debugger:latest'), - (5, 1, 'ghcr.io/datasance/nats:latest'), - (5, 2, 'ghcr.io/datasance/nats:latest'); + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'); COMMIT; \ No newline at end of file diff --git a/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql b/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql index 0dfb66fe..a4c61138 100644 --- a/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql +++ b/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql @@ -7,11 +7,11 @@ VALUES INSERT INTO "CatalogItems" (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) VALUES - ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('Router', 'The built-in router for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('Debug', 'The built-in debugger for Datasance PoT Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + ('Debug', 'The built-in debugger for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Edgelet', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); INSERT INTO "Architectures" (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) VALUES @@ -27,26 +27,26 @@ WHERE arch_id IS NULL; INSERT INTO "CatalogItemImages" (catalog_item_id, arch_id, container_image) VALUES - (1, 1, 'ghcr.io/datasance/router:latest'), - (1, 2, 'ghcr.io/datasance/router:latest'), - (1, 3, 'ghcr.io/datasance/router:latest'), - (1, 4, 'ghcr.io/datasance/router:latest'), - (2, 1, 'ghcr.io/datasance/restblue:latest'), - (2, 2, 'ghcr.io/datasance/restblue:latest'), - (2, 3, 'ghcr.io/datasance/restblue:latest'), - (2, 4, 'ghcr.io/datasance/restblue:latest'), - (3, 1, 'ghcr.io/datasance/hal:latest'), - (3, 2, 'ghcr.io/datasance/hal:latest'), - (3, 3, 'ghcr.io/datasance/hal:latest'), - (3, 4, 'ghcr.io/datasance/hal:latest'), - (4, 1, 'ghcr.io/datasance/node-debugger:latest'), - (4, 2, 'ghcr.io/datasance/node-debugger:latest'), - (4, 3, 'ghcr.io/datasance/node-debugger:latest'), - (4, 4, 'ghcr.io/datasance/node-debugger:latest'), - (5, 1, 'ghcr.io/datasance/nats:latest'), - (5, 2, 'ghcr.io/datasance/nats:latest'), - (5, 3, 'ghcr.io/datasance/nats:latest'), - (5, 4, 'ghcr.io/datasance/nats:latest'); + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 3, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 4, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 3, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 4, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 3, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 4, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 3, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 4, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 3, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 4, 'ghcr.io/eclipse-iofog/nats:latest'); INSERT INTO "AuthPolicy" ( id, diff --git a/src/data/seeders/sqlite/db_seeder_sqlite_v1.0.2.sql b/src/data/seeders/sqlite/db_seeder_sqlite_v1.0.2.sql index 1f5b1039..a51f6db0 100644 --- a/src/data/seeders/sqlite/db_seeder_sqlite_v1.0.2.sql +++ b/src/data/seeders/sqlite/db_seeder_sqlite_v1.0.2.sql @@ -5,11 +5,11 @@ VALUES INSERT INTO `CatalogItems` (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) VALUES - ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('Router', 'The built-in router for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('Debug', 'The built-in debugger for Datasance PoT IoFog Agent.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + ('Debug', 'The built-in debugger for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Edgelet', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); INSERT INTO `FogTypes` (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) VALUES @@ -23,13 +23,13 @@ WHERE fog_type_id IS NULL; INSERT INTO `CatalogItemImages` (catalog_item_id, fog_type_id, container_image) VALUES - (1, 1, 'ghcr.io/datasance/router:latest'), - (1, 2, 'ghcr.io/datasance/router:latest'), - (2, 1, 'ghcr.io/datasance/restblue:latest'), - (2, 2, 'ghcr.io/datasance/restblue:latest'), - (3, 1, 'ghcr.io/datasance/hal:latest'), - (3, 2, 'ghcr.io/datasance/hal:latest'), - (4, 1, 'ghcr.io/datasance/node-debugger:latest'), - (4, 2, 'ghcr.io/datasance/node-debugger:latest'), - (5, 1, 'ghcr.io/datasance/nats:latest'), - (5, 2, 'ghcr.io/datasance/nats:latest'); + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'); diff --git a/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql b/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql index 8cf2c81c..95497747 100644 --- a/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql +++ b/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql @@ -5,11 +5,11 @@ VALUES INSERT INTO `CatalogItems` (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) VALUES - ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('Router', 'The built-in router for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('Debug', 'The built-in debugger for Datasance PoT Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + ('Debug', 'The built-in debugger for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Edgelet', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); INSERT INTO `Architectures` (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) VALUES @@ -25,26 +25,26 @@ WHERE arch_id IS NULL; INSERT INTO `CatalogItemImages` (catalog_item_id, arch_id, container_image) VALUES - (1, 1, 'ghcr.io/datasance/router:latest'), - (1, 2, 'ghcr.io/datasance/router:latest'), - (1, 3, 'ghcr.io/datasance/router:latest'), - (1, 4, 'ghcr.io/datasance/router:latest'), - (2, 1, 'ghcr.io/datasance/restblue:latest'), - (2, 2, 'ghcr.io/datasance/restblue:latest'), - (2, 3, 'ghcr.io/datasance/restblue:latest'), - (2, 4, 'ghcr.io/datasance/restblue:latest'), - (3, 1, 'ghcr.io/datasance/hal:latest'), - (3, 2, 'ghcr.io/datasance/hal:latest'), - (3, 3, 'ghcr.io/datasance/hal:latest'), - (3, 4, 'ghcr.io/datasance/hal:latest'), - (4, 1, 'ghcr.io/datasance/node-debugger:latest'), - (4, 2, 'ghcr.io/datasance/node-debugger:latest'), - (4, 3, 'ghcr.io/datasance/node-debugger:latest'), - (4, 4, 'ghcr.io/datasance/node-debugger:latest'), - (5, 1, 'ghcr.io/datasance/nats:latest'), - (5, 2, 'ghcr.io/datasance/nats:latest'), - (5, 3, 'ghcr.io/datasance/nats:latest'), - (5, 4, 'ghcr.io/datasance/nats:latest'); + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 3, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 4, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 3, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 4, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 3, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 4, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 3, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 4, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 3, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 4, 'ghcr.io/eclipse-iofog/nats:latest'); INSERT OR IGNORE INTO AuthPolicy ( id, diff --git a/src/decorators/authorization-decorator.js b/src/decorators/authorization-decorator.js index 0a08b0d4..dc10a692 100644 --- a/src/decorators/authorization-decorator.js +++ b/src/decorators/authorization-decorator.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const logger = require('../logger') const FogManager = require('../data/managers/iofog-manager') const FogKeyService = require('../services/iofog-key-service') diff --git a/src/decorators/response-decorator.js b/src/decorators/response-decorator.js index 1fd9c016..c8e392c9 100644 --- a/src/decorators/response-decorator.js +++ b/src/decorators/response-decorator.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const logger = require('../logger') const { isTest } = require('../helpers/app-helper') diff --git a/src/decorators/transaction-decorator.js b/src/decorators/transaction-decorator.js index 447bf1b5..c49b8dc2 100644 --- a/src/decorators/transaction-decorator.js +++ b/src/decorators/transaction-decorator.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const cq = require('concurrent-queue') const Transaction = require('sequelize/lib/transaction') diff --git a/src/enums/fog-state.js b/src/enums/fog-state.js index 2c7d712a..b159e332 100644 --- a/src/enums/fog-state.js +++ b/src/enums/fog-state.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const fogState = { UNKNOWN: 'UNKNOWN', RUNNING: 'RUNNING', diff --git a/src/enums/microservice-state.js b/src/enums/microservice-state.js index ac985f44..63970e4c 100644 --- a/src/enums/microservice-state.js +++ b/src/enums/microservice-state.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const microserviceState = { QUEUED: 'QUEUED', PULLING: 'PULLING', diff --git a/src/helpers/app-helper.js b/src/helpers/app-helper.js index 82f37c2c..6df81381 100644 --- a/src/helpers/app-helper.js +++ b/src/helpers/app-helper.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const crypto = require('crypto') const Errors = require('./errors') // const { v4: uuidv4 } = require('uuid') diff --git a/src/helpers/cert-dns-sans.js b/src/helpers/cert-dns-sans.js index 15558241..2662db9c 100644 --- a/src/helpers/cert-dns-sans.js +++ b/src/helpers/cert-dns-sans.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const config = require('../config') const Constants = require('./constants') diff --git a/src/helpers/constants.js b/src/helpers/constants.js index deab8eb6..7abfe6b7 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const path = require('path') module.exports = { diff --git a/src/helpers/error-messages.js b/src/helpers/error-messages.js index e848a29a..6b31a8ed 100644 --- a/src/helpers/error-messages.js +++ b/src/helpers/error-messages.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - module.exports = { INVALID_CLI_ARGUMENT_TYPE: 'Field "{}" is not of type(s) {}', DUPLICATE_NAME: 'Duplicate name \'{}\'', diff --git a/src/helpers/errors.js b/src/helpers/errors.js index d6be8f55..3e8b20fe 100644 --- a/src/helpers/errors.js +++ b/src/helpers/errors.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - class AuthenticationError extends Error { constructor (message) { super(message) diff --git a/src/helpers/system-naming.js b/src/helpers/system-naming.js index f3a7132e..3570ff60 100644 --- a/src/helpers/system-naming.js +++ b/src/helpers/system-naming.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const logger = require('../logger') const Errors = require('./errors') const ApplicationManager = require('../data/managers/application-manager') diff --git a/src/helpers/template-helper.js b/src/helpers/template-helper.js index 21eca1e5..725381ba 100755 --- a/src/helpers/template-helper.js +++ b/src/helpers/template-helper.js @@ -1,16 +1,3 @@ -/* - * Software Name : eclipse-iofog/Controller - * Version: 2.0.x - * SPDX-FileCopyrightText: Copyright (c) 2020-2020 Orange - * SPDX-License-Identifier: EPL-2.0 - * - * This software is distributed under the , - * the text of which is available at http://www.eclipse.org/legal/epl-2.0 - * or see the "license.txt" file for more details. - * - * Author: Franck Roudet - */ - const ApplicationManager = require('../data/managers/application-manager.js') // Using manager instead of service to avoid dependency loop const FogService = require('../services/iofog-service') const MicroservicesService = require('../services/microservices-service') diff --git a/src/init.js b/src/init.js index 7bcc409c..22b398ee 100644 --- a/src/init.js +++ b/src/init.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - // Load configuration first require('./config') diff --git a/src/jobs/controller-cleanup-job.js b/src/jobs/controller-cleanup-job.js index 449ac4ef..732693b3 100644 --- a/src/jobs/controller-cleanup-job.js +++ b/src/jobs/controller-cleanup-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ClusterControllerManager = require('../data/managers/cluster-controller-manager') const Config = require('../config') const logger = require('../logger') diff --git a/src/jobs/controller-heartbeat-job.js b/src/jobs/controller-heartbeat-job.js index 3b960733..85517ad8 100644 --- a/src/jobs/controller-heartbeat-job.js +++ b/src/jobs/controller-heartbeat-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ClusterControllerService = require('../services/cluster-controller-service') const Config = require('../config') const logger = require('../logger') diff --git a/src/jobs/event-cleanup-job.js b/src/jobs/event-cleanup-job.js index 7ff0b74e..64dc8d61 100644 --- a/src/jobs/event-cleanup-job.js +++ b/src/jobs/event-cleanup-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const EventManager = require('../data/managers/event-manager') const EventService = require('../services/event-service') const Config = require('../config') diff --git a/src/jobs/fog-status-job.js b/src/jobs/fog-status-job.js index 2d1f167c..11fd62ac 100644 --- a/src/jobs/fog-status-job.js +++ b/src/jobs/fog-status-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TransactionDecorator = require('../decorators/transaction-decorator') const FogManager = require('../data/managers/iofog-manager') diff --git a/src/jobs/fog-token-cleanup-job.js b/src/jobs/fog-token-cleanup-job.js index 1cce9459..cb433b4a 100644 --- a/src/jobs/fog-token-cleanup-job.js +++ b/src/jobs/fog-token-cleanup-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const FogUsedTokenManager = require('../data/managers/fog-used-token-manager') const Config = require('../config') const logger = require('../logger') diff --git a/src/jobs/nats-reconcile-worker-job.js b/src/jobs/nats-reconcile-worker-job.js index 02a28275..cad203d2 100644 --- a/src/jobs/nats-reconcile-worker-job.js +++ b/src/jobs/nats-reconcile-worker-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ClusterControllerService = require('../services/cluster-controller-service') const NatsService = require('../services/nats-service') const NatsReconcileTaskManager = require('../data/managers/nats-reconcile-task-manager') diff --git a/src/jobs/stopped-app-status-job.js b/src/jobs/stopped-app-status-job.js index ea07f2bd..36057e86 100644 --- a/src/jobs/stopped-app-status-job.js +++ b/src/jobs/stopped-app-status-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TransactionDecorator = require('../decorators/transaction-decorator') const MicroserviceManager = require('../data/managers/microservice-manager') diff --git a/src/keycloak.json b/src/keycloak.json deleted file mode 100644 index cfc7f6b3..00000000 --- a/src/keycloak.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "realm": "datasance", - "realm-public-key": "", - "auth-server-url": "", - "ssl-required": "", - "resource": "pot-controller", - "bearer-only":true, - "verify-token-audience": true, - "credentials": { - "secret": "" - }, - "use-resource-role-mappings": true, - "confidential-port": 0 -} \ No newline at end of file diff --git a/src/lib/rbac/authorizer.js b/src/lib/rbac/authorizer.js index 520be971..c2427b03 100644 --- a/src/lib/rbac/authorizer.js +++ b/src/lib/rbac/authorizer.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const RbacRoleBindingManager = require('../../data/managers/rbac-role-binding-manager') const RbacRoleManager = require('../../data/managers/rbac-role-manager') const RbacCacheVersionManager = require('../../data/managers/rbac-cache-version-manager') diff --git a/src/lib/rbac/middleware.js b/src/lib/rbac/middleware.js index 2410050a..e1cbf344 100644 --- a/src/lib/rbac/middleware.js +++ b/src/lib/rbac/middleware.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const fs = require('fs') const path = require('path') const yaml = require('js-yaml') diff --git a/src/logger/index.js b/src/logger/index.js index 91548799..b23f4eb6 100644 --- a/src/logger/index.js +++ b/src/logger/index.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const pino = require('pino') const path = require('path') const fs = require('fs') diff --git a/src/main.js b/src/main.js index 1528a2f1..d959b2ee 100644 --- a/src/main.js +++ b/src/main.js @@ -1,18 +1,5 @@ #!/usr/bin/env node -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const Cli = require('./cli') const daemon = require('./daemon') const config = require('./config') diff --git a/src/middlewares/auth-rate-limit-middleware.js b/src/middlewares/auth-rate-limit-middleware.js index b8e37b3c..46d65233 100644 --- a/src/middlewares/auth-rate-limit-middleware.js +++ b/src/middlewares/auth-rate-limit-middleware.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const config = require('../config') const constants = require('../helpers/constants') const logger = require('../logger') diff --git a/src/middlewares/event-audit-middleware.js b/src/middlewares/event-audit-middleware.js index af1a9892..f48a3944 100644 --- a/src/middlewares/event-audit-middleware.js +++ b/src/middlewares/event-audit-middleware.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const EventService = require('../services/event-service') const config = require('../config') const logger = require('../logger') diff --git a/src/routes/agent.js b/src/routes/agent.js index d7798429..b25ca68a 100644 --- a/src/routes/agent.js +++ b/src/routes/agent.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const AgentController = require('../controllers/agent-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/application.js b/src/routes/application.js index b52fb33f..5388db65 100644 --- a/src/routes/application.js +++ b/src/routes/application.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const ApplicationController = require('../controllers/application-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/applicationTemplate.js b/src/routes/applicationTemplate.js index ea892da1..d94ce6b1 100644 --- a/src/routes/applicationTemplate.js +++ b/src/routes/applicationTemplate.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const ApplicationTemplateController = require('../controllers/application-template-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/auth.js b/src/routes/auth.js index c3499a45..15442a93 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const AuthController = require('../controllers/auth-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/capabilities.js b/src/routes/capabilities.js index 847eb2cb..f2d5723e 100644 --- a/src/routes/capabilities.js +++ b/src/routes/capabilities.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const logger = require('../logger') const config = require('../config') const rbacMiddleware = require('../lib/rbac/middleware') diff --git a/src/routes/catalog.js b/src/routes/catalog.js index ed3177c4..726987db 100644 --- a/src/routes/catalog.js +++ b/src/routes/catalog.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const CatalogController = require('../controllers/catalog-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/cluster.js b/src/routes/cluster.js index db4e6fb3..67c3a4aa 100644 --- a/src/routes/cluster.js +++ b/src/routes/cluster.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const ClusterController = require('../controllers/cluster-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/config.js b/src/routes/config.js index 412a2b57..cfc361a9 100644 --- a/src/routes/config.js +++ b/src/routes/config.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const ConfigController = require('../controllers/config-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/configMap.js b/src/routes/configMap.js index 81f65907..6935d181 100644 --- a/src/routes/configMap.js +++ b/src/routes/configMap.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const ConfigMapController = require('../controllers/config-map-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/controller.js b/src/routes/controller.js index 3da79261..bff7924a 100644 --- a/src/routes/controller.js +++ b/src/routes/controller.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const Controller = require('../controllers/controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/event.js b/src/routes/event.js index cee2605d..a7aab2f9 100644 --- a/src/routes/event.js +++ b/src/routes/event.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const EventController = require('../controllers/event-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/iofog.js b/src/routes/iofog.js index 7cc0a077..cf981377 100644 --- a/src/routes/iofog.js +++ b/src/routes/iofog.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const FogController = require('../controllers/iofog-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/microservices.js b/src/routes/microservices.js index bae0e97b..b3de3939 100644 --- a/src/routes/microservices.js +++ b/src/routes/microservices.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const MicroservicesController = require('../controllers/microservices-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/nats.js b/src/routes/nats.js index 7adaba00..7d433b6d 100644 --- a/src/routes/nats.js +++ b/src/routes/nats.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const NatsController = require('../controllers/nats-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/rbac.js b/src/routes/rbac.js index 85966de8..348ddf5e 100644 --- a/src/routes/rbac.js +++ b/src/routes/rbac.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const RbacController = require('../controllers/rbac-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/registries.js b/src/routes/registries.js index d0bf9e2b..8fea7d10 100644 --- a/src/routes/registries.js +++ b/src/routes/registries.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const RegistryController = require('../controllers/registry-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/router.js b/src/routes/router.js index 2b29ef30..b850d125 100644 --- a/src/routes/router.js +++ b/src/routes/router.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const Router = require('../controllers/router-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/secret.js b/src/routes/secret.js index c4dbff8a..113887d5 100644 --- a/src/routes/secret.js +++ b/src/routes/secret.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const SecretController = require('../controllers/secret-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/service.js b/src/routes/service.js index 7247a783..eb8670a0 100644 --- a/src/routes/service.js +++ b/src/routes/service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const ServiceController = require('../controllers/service-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/tunnel.js b/src/routes/tunnel.js index ce286eed..574602ea 100644 --- a/src/routes/tunnel.js +++ b/src/routes/tunnel.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const TunnelController = require('../controllers/tunnel-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/user.js b/src/routes/user.js index a52c9258..5cdb0632 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const UserController = require('../controllers/user-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/users.js b/src/routes/users.js index 8810fdf9..e16304d3 100644 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const UsersController = require('../controllers/users-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/routes/volumeMount.js b/src/routes/volumeMount.js index 0e8d1f15..ee206656 100644 --- a/src/routes/volumeMount.js +++ b/src/routes/volumeMount.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const VolumeMountController = require('../controllers/volume-mount-controller') const ResponseDecorator = require('../decorators/response-decorator') diff --git a/src/schemas/agent.js b/src/schemas/agent.js index 023f155d..4b5ffbcd 100644 --- a/src/schemas/agent.js +++ b/src/schemas/agent.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const agentProvision = { id: '/agentProvision', type: 'object', diff --git a/src/schemas/catalog.js b/src/schemas/catalog.js index d36fee2f..0ad2eb1a 100644 --- a/src/schemas/catalog.js +++ b/src/schemas/catalog.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const catalogItemCreate = { id: '/catalogItemCreate', type: 'object', diff --git a/src/schemas/cluster-controller.js b/src/schemas/cluster-controller.js index 18b00f30..c9cecbe0 100644 --- a/src/schemas/cluster-controller.js +++ b/src/schemas/cluster-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const clusterControllerUpdate = { id: '/clusterControllerUpdate', type: 'object', diff --git a/src/schemas/config.js b/src/schemas/config.js index 5c4e390b..69692b94 100644 --- a/src/schemas/config.js +++ b/src/schemas/config.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const configUpdate = { id: '/configUpdate', type: 'object', diff --git a/src/schemas/controlPlane.js b/src/schemas/controlPlane.js index 79b659a9..8a0e8710 100644 --- a/src/schemas/controlPlane.js +++ b/src/schemas/controlPlane.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const details = { id: '/profile', type: 'object', diff --git a/src/schemas/controller-register.js b/src/schemas/controller-register.js index 76cf3ddf..d0659a15 100644 --- a/src/schemas/controller-register.js +++ b/src/schemas/controller-register.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const controllerRegister = { id: '/controllerRegister', type: 'object', diff --git a/src/schemas/index.js b/src/schemas/index.js index 3ec077aa..ac8ffe32 100644 --- a/src/schemas/index.js +++ b/src/schemas/index.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const Validator = require('jsonschema').Validator const fs = require('fs') const path = require('path') diff --git a/src/schemas/iofog.js b/src/schemas/iofog.js index 4683c3a7..91f0ce48 100644 --- a/src/schemas/iofog.js +++ b/src/schemas/iofog.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const iofogCreate = { id: '/iofogCreate', type: 'object', diff --git a/src/schemas/rbac.js b/src/schemas/rbac.js index e9b77efc..93e0a9a8 100644 --- a/src/schemas/rbac.js +++ b/src/schemas/rbac.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const { nameRegex } = require('./utils/utils') // Inner Schema: RBAC Rule diff --git a/src/schemas/registry.js b/src/schemas/registry.js index 3bae19ad..e07a46d7 100644 --- a/src/schemas/registry.js +++ b/src/schemas/registry.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const registryCreate = { id: '/registryCreate', type: 'object', diff --git a/src/schemas/tunnel.js b/src/schemas/tunnel.js index e2b2d9af..fe303532 100644 --- a/src/schemas/tunnel.js +++ b/src/schemas/tunnel.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const tunnelCreate = { id: '/tunnelCreate', type: 'object', diff --git a/src/schemas/user.js b/src/schemas/user.js index 638491da..bd965abb 100644 --- a/src/schemas/user.js +++ b/src/schemas/user.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const login = { id: '/login', type: 'object', diff --git a/src/server.js b/src/server.js index 9d82392d..e07126ad 100755 --- a/src/server.js +++ b/src/server.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - // Initialize everything in the correct order const { initialize } = require('./init') initialize().then(() => { diff --git a/src/services/agent-service.js b/src/services/agent-service.js index 045bb1ca..fac63cc4 100644 --- a/src/services/agent-service.js +++ b/src/services/agent-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const config = require('../config') const fs = require('fs') // const Sequelize = require('sequelize') diff --git a/src/services/application-service.js b/src/services/application-service.js index 6f67bee6..dd795eb9 100644 --- a/src/services/application-service.js +++ b/src/services/application-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const Sequelize = require('sequelize') const Op = Sequelize.Op diff --git a/src/services/application-template-service.js b/src/services/application-template-service.js index f2c755ca..7353ca39 100644 --- a/src/services/application-template-service.js +++ b/src/services/application-template-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const Sequelize = require('sequelize') const Op = Sequelize.Op diff --git a/src/services/catalog-service.js b/src/services/catalog-service.js index bca66847..d8eef9a1 100644 --- a/src/services/catalog-service.js +++ b/src/services/catalog-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TransactionDecorator = require('../decorators/transaction-decorator') const AppHelper = require('../helpers/app-helper') const { validateUniqueArchIds } = require('../helpers/arch-images') diff --git a/src/services/change-tracking-service.js b/src/services/change-tracking-service.js index d3536435..3174fd74 100644 --- a/src/services/change-tracking-service.js +++ b/src/services/change-tracking-service.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const moment = require('moment') const Op = require('sequelize').Op diff --git a/src/services/cluster-controller-service.js b/src/services/cluster-controller-service.js index 8725c4c5..39b4b7e6 100644 --- a/src/services/cluster-controller-service.js +++ b/src/services/cluster-controller-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const os = require('os') const AppHelper = require('../helpers/app-helper') const ErrorMessages = require('../helpers/error-messages') diff --git a/src/services/config-map-service.js b/src/services/config-map-service.js index 88420558..0c6ddd9f 100644 --- a/src/services/config-map-service.js +++ b/src/services/config-map-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TransactionDecorator = require('../decorators/transaction-decorator') const ConfigMapManager = require('../data/managers/config-map-manager') const MicroserviceManager = require('../data/managers/microservice-manager') diff --git a/src/services/config-service.js b/src/services/config-service.js index 63cab6f6..5fa429be 100644 --- a/src/services/config-service.js +++ b/src/services/config-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const AppHelper = require('../helpers/app-helper') const Errors = require('../helpers/errors') const ErrorMessages = require('../helpers/error-messages') diff --git a/src/services/controller-ms-service.js b/src/services/controller-ms-service.js index d77d21a6..e417f3c2 100644 --- a/src/services/controller-ms-service.js +++ b/src/services/controller-ms-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TransactionDecorator = require('../decorators/transaction-decorator') const Validator = require('../schemas/index') const Errors = require('../helpers/errors') diff --git a/src/services/controller-service.js b/src/services/controller-service.js index 7bbdeec0..87e3393e 100644 --- a/src/services/controller-service.js +++ b/src/services/controller-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const fs = require('fs') const path = require('path') diff --git a/src/services/event-service.js b/src/services/event-service.js index 203ee46c..580381fe 100644 --- a/src/services/event-service.js +++ b/src/services/event-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const EventManager = require('../data/managers/event-manager') const config = require('../config') const logger = require('../logger') diff --git a/src/services/iofog-key-service.js b/src/services/iofog-key-service.js index a5c2ee8f..13be6280 100644 --- a/src/services/iofog-key-service.js +++ b/src/services/iofog-key-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const crypto = require('crypto') const FogPublicKeyManager = require('../data/managers/iofog-public-key-manager') const FogUsedTokenManager = require('../data/managers/fog-used-token-manager') diff --git a/src/services/microservice-ports/microservice-port.js b/src/services/microservice-ports/microservice-port.js index e866f191..9e541633 100644 --- a/src/services/microservice-ports/microservice-port.js +++ b/src/services/microservice-ports/microservice-port.js @@ -1,16 +1,3 @@ -/* only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const MicroservicePortManager = require('../../data/managers/microservice-port-manager') const MicroserviceManager = require('../../data/managers/microservice-manager') const ChangeTrackingService = require('../change-tracking-service') diff --git a/src/services/microservices-service.js b/src/services/microservices-service.js index 29631e1e..0bd20ebc 100644 --- a/src/services/microservices-service.js +++ b/src/services/microservices-service.js @@ -1,16 +1,3 @@ -/* only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TransactionDecorator = require('../decorators/transaction-decorator') const MicroserviceManager = require('../data/managers/microservice-manager') const MicroserviceStatusManager = require('../data/managers/microservice-status-manager') diff --git a/src/services/nats-api-service.js b/src/services/nats-api-service.js index 13903b66..9358f25c 100644 --- a/src/services/nats-api-service.js +++ b/src/services/nats-api-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const NatsAuthService = require('./nats-auth-service') const NatsHubService = require('./nats-hub-service') const NatsAccountManager = require('../data/managers/nats-account-manager') diff --git a/src/services/nats-auth-service.js b/src/services/nats-auth-service.js index df49666c..623bd1eb 100644 --- a/src/services/nats-auth-service.js +++ b/src/services/nats-auth-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const { createOperator, createAccount, createUser, fromSeed, encodeOperator, encodeAccount, encodeUser, fmtCreds } = require('@nats-io/jwt') const AppHelper = require('../helpers/app-helper') const Errors = require('../helpers/errors') diff --git a/src/services/nats-hub-service.js b/src/services/nats-hub-service.js index 82264584..53f2d12b 100644 --- a/src/services/nats-hub-service.js +++ b/src/services/nats-hub-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const Errors = require('../helpers/errors') const Validator = require('../schemas') const NatsInstanceManager = require('../data/managers/nats-instance-manager') diff --git a/src/services/nats-service.js b/src/services/nats-service.js index ff2a0097..1e14221c 100644 --- a/src/services/nats-service.js +++ b/src/services/nats-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const fs = require('fs') const path = require('path') const crypto = require('crypto') diff --git a/src/services/rbac-service.js b/src/services/rbac-service.js index 1d33f7a9..f6c94413 100644 --- a/src/services/rbac-service.js +++ b/src/services/rbac-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const RbacRoleManager = require('../data/managers/rbac-role-manager') const RbacRoleBindingManager = require('../data/managers/rbac-role-binding-manager') const RbacServiceAccountManager = require('../data/managers/rbac-service-account-manager') diff --git a/src/services/registry-service.js b/src/services/registry-service.js index 5362907f..41985fdc 100644 --- a/src/services/registry-service.js +++ b/src/services/registry-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const RegistryManager = require('../data/managers/registry-manager') const SecretHelper = require('../helpers/secret-helper') const Validator = require('../schemas') diff --git a/src/services/router-service.js b/src/services/router-service.js index 8841c4a0..2b48611f 100644 --- a/src/services/router-service.js +++ b/src/services/router-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const AppHelper = require('../helpers/app-helper') const CatalogService = require('../services/catalog-service') const ChangeTrackingService = require('../services/change-tracking-service') @@ -40,7 +27,7 @@ const { getSystemMicroserviceName } = require('../helpers/system-naming') -const SITE_CONFIG_VERSION = 'pot' +const SITE_CONFIG_VERSION = 'iofog' const SITE_CONFIG_NAMESPACE = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace') const SSL_PROFILE_PATH = '/etc/skupper-router-certs' const SYSTEM_DEFAULT_CA_PATH = '/etc/pki/tls/certs/ca-bundle.crt' @@ -381,7 +368,7 @@ async function _createRouterMicroservice (isEdge, uuid, microserviceConfig, tran }, { key: 'SKUPPER_PLATFORM', - value: 'pot' + value: 'iofog' } ] } diff --git a/src/services/secret-service.js b/src/services/secret-service.js index 1aaf5aa8..59500b20 100644 --- a/src/services/secret-service.js +++ b/src/services/secret-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const crypto = require('crypto') const TransactionDecorator = require('../decorators/transaction-decorator') const SecretManager = require('../data/managers/secret-manager') diff --git a/src/services/tunnel-service.js b/src/services/tunnel-service.js index 8dd4efd0..3feb122e 100644 --- a/src/services/tunnel-service.js +++ b/src/services/tunnel-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TunnelManager = require('../data/managers/tunnel-manager') const FogManager = require('../data/managers/iofog-manager') const Config = require('../config') diff --git a/src/services/user-service.js b/src/services/user-service.js index 8d3ccace..d08f69b9 100644 --- a/src/services/user-service.js +++ b/src/services/user-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const Errors = require('../helpers/errors') const TransactionDecorator = require('../decorators/transaction-decorator') const { diff --git a/src/utils/ssl-utils.js b/src/utils/ssl-utils.js index 6a0a1200..37af624b 100644 --- a/src/utils/ssl-utils.js +++ b/src/utils/ssl-utils.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const fs = require('fs') const logger = require('../logger') diff --git a/src/vault/aws-secrets-manager-provider.js b/src/vault/aws-secrets-manager-provider.js index 57be88bc..66aeca30 100644 --- a/src/vault/aws-secrets-manager-provider.js +++ b/src/vault/aws-secrets-manager-provider.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseVaultProvider = require('./base-vault-provider') const logger = require('../logger') @@ -77,7 +64,7 @@ class AWSSecretsManagerProvider extends BaseVaultProvider { const command = new this.CreateSecretCommand({ Name: secretName, SecretString: JSON.stringify(data), - Description: `Datasance PoT controller secret: ${path}` + Description: `Controller secret: ${path}` }) try { @@ -184,7 +171,7 @@ class AWSSecretsManagerProvider extends BaseVaultProvider { if (this.config && this.config.basePath && typeof this.config.basePath === 'string') { return this.config.basePath } - return 'pot-controller/secrets' + return 'controller/secrets' } } diff --git a/src/vault/azure-key-vault-provider.js b/src/vault/azure-key-vault-provider.js index 7158ba9a..2d54f633 100644 --- a/src/vault/azure-key-vault-provider.js +++ b/src/vault/azure-key-vault-provider.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseVaultProvider = require('./base-vault-provider') class AzureKeyVaultProvider extends BaseVaultProvider { @@ -159,7 +146,7 @@ class AzureKeyVaultProvider extends BaseVaultProvider { if (this.config && this.config.basePath && typeof this.config.basePath === 'string') { return this.config.basePath } - return 'pot-controller/secrets' + return 'controller/secrets' } } diff --git a/src/vault/base-vault-provider.js b/src/vault/base-vault-provider.js index f509c2c1..b93ffd94 100644 --- a/src/vault/base-vault-provider.js +++ b/src/vault/base-vault-provider.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - /** * Base class for vault providers * All vault providers must implement this interface diff --git a/src/vault/google-secret-manager-provider.js b/src/vault/google-secret-manager-provider.js index f5f222a7..cd6167e7 100644 --- a/src/vault/google-secret-manager-provider.js +++ b/src/vault/google-secret-manager-provider.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseVaultProvider = require('./base-vault-provider') class GoogleSecretManagerProvider extends BaseVaultProvider { @@ -215,7 +202,7 @@ class GoogleSecretManagerProvider extends BaseVaultProvider { if (this.config && this.config.basePath && typeof this.config.basePath === 'string') { return this.config.basePath } - return 'pot-controller/secrets' + return 'controller/secrets' } } diff --git a/src/vault/hashicorp-vault-provider.js b/src/vault/hashicorp-vault-provider.js index ab6da75b..0c5373c1 100644 --- a/src/vault/hashicorp-vault-provider.js +++ b/src/vault/hashicorp-vault-provider.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseVaultProvider = require('./base-vault-provider') const https = require('https') const http = require('http') @@ -224,7 +211,7 @@ class HashiCorpVaultProvider extends BaseVaultProvider { if (this.config && this.config.basePath && typeof this.config.basePath === 'string') { return this.config.basePath } - return 'pot-controller/secrets' + return 'controller/secrets' } } diff --git a/src/vault/vault-manager.js b/src/vault/vault-manager.js index bc2c2f0f..843df2f2 100644 --- a/src/vault/vault-manager.js +++ b/src/vault/vault-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const HashiCorpVaultProvider = require('./hashicorp-vault-provider') const AWSSecretsManagerProvider = require('./aws-secrets-manager-provider') const AzureKeyVaultProvider = require('./azure-key-vault-provider') @@ -30,7 +17,7 @@ class VaultManager { */ _getBasePath () { // Get basePath from env var or config, with default - const basePath = process.env.VAULT_BASE_PATH || config.get('vault.basePath', 'pot/$namespace/secrets') + const basePath = process.env.VAULT_BASE_PATH || config.get('vault.basePath', 'iofog/$namespace/secrets') const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'datasance') // Replace $namespace variable diff --git a/src/websocket/log-session-manager.js b/src/websocket/log-session-manager.js index ae033596..1e58d38b 100644 --- a/src/websocket/log-session-manager.js +++ b/src/websocket/log-session-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const WebSocket = require('ws') const logger = require('../logger') const MicroserviceLogStatusManager = require('../data/managers/microservice-log-status-manager') From fdd34460440c7ed138bb4077f466ed4aed1c84d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 03:34:57 +0300 Subject: [PATCH 65/75] Document dual-mirror repository model and per-mirror CI variables. Describe shared git tree across eclipse-iofog and Datasance remotes, preflight vs tag-only publish workflow, and required GitHub Actions variables. --- CONTRIBUTING | 38 +++++++++++++++++++++++++++++++++++++- README.md | 2 ++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING b/CONTRIBUTING index 73d4728e..c4947cac 100644 --- a/CONTRIBUTING +++ b/CONTRIBUTING @@ -1,7 +1,43 @@ -# Contributing to Eclipse-ioFog Controller +# Contributing to Controller Thanks for your interest in this project. +## Dual-mirror repositories + +Controller v3.8 uses a **single shared git tree** published to two GitHub remotes at the **same commit SHA**: + +| Remote | Default registry | OCI source | +|--------|------------------|------------| +| [eclipse-iofog/Controller](https://github.com/eclipse-iofog/Controller) | `ghcr.io/eclipse-iofog` | `https://github.com/eclipse-iofog/Controller` | +| [Datasance/Controller](https://github.com/Datasance/Controller) | `ghcr.io/datasance` | `https://github.com/Datasance/Controller` | + +Canonical upstream in `package.json` is `eclipse-iofog/Controller`. Product-facing strings (RBAC API group, distribution label, container registry) differ by **build flavor**, not by diverging source code. + +### CI workflow + +| Trigger | Jobs | +|---------|------| +| Pull request, push to `main`, push to `develop` | **Preflight** — lint, unit tests, dependency audit, Docker build (**no image push**) | +| Push of a `v*` tag (e.g. `v3.8.0`) | **Publish** — Docker build and push to `${IMAGE_REGISTRY}/controller` | + +Image publish runs **only** on version tags. Integration branches on both mirrors are `main` and `develop`. + +### Repository CI variables + +Set these as GitHub **Actions variables** (`Settings → Secrets and variables → Actions → Variables`) per mirror. When unset, CI derives defaults from `github.repository` (same pattern as EdgeOps Console). + +| Variable | Eclipse ioFog mirror | Datasance PoT mirror | +|----------|----------------------|----------------------| +| `IMAGE_REGISTRY` | `ghcr.io/eclipse-iofog` | `ghcr.io/datasance` | +| `OCI_SOURCE_REPO` | `https://github.com/eclipse-iofog/Controller` | `https://github.com/Datasance/Controller` | +| `CONTROLLER_DISTRIBUTION` | `iofog` | `datasance` | +| `RBAC_API_VERSION` | `iofog.org/v3` | `datasance.com/v3` | +| `EDGEOPS_CONSOLE_REPO` | `https://github.com/eclipse-iofog/edgeops-console` | `https://github.com/Datasance/edgeops-console` | +| `EDGEOPS_CONSOLE_FLAVOR` | `iofog` | `datasance` | +| `EDGEOPS_CONSOLE_VERSION` | Console release tag (optional; default `1.0.0`) | same | + +Docker build passes these as build-args for OCI labels and baked runtime flavor env. + ## Project description Controller acts as your entry-point to interacting with, deploying to, and maintaining any ECN or Edge cluster of Agent Nodes you have. diff --git a/README.md b/README.md index da3e8d7c..331e4bcc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # ioFog Controller +See [CONTRIBUTING](CONTRIBUTING) for the dual-mirror repository model and CI workflow. Full operator-facing README updates are planned for the v3.8 release (Plan 13). + ### Status ![](https://img.shields.io/github/release/datasance/controller.svg?style=flat) From 53023355834abddf9b901a3c419f44ad53402241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 03:35:32 +0300 Subject: [PATCH 66/75] Naming convention across swagger --- docs/swagger.json | 2 +- docs/swagger.yaml | 4 ++-- scripts/util.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/swagger.json b/docs/swagger.json index 17772b49..991d6f19 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -2,7 +2,7 @@ "openapi": "3.0.0", "info": { "version": "1.0.0", - "title": "Datasance PoT Controller" + "title": "Cloud Natice Controller for Edge Native Workloads" }, "tags": [ { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 998b1fc4..381347ae 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,7 +1,7 @@ openapi : "3.0.0" info: version: 3.8.0 - title: Edge Compute Network Controller + title: Cloud Natice Controller for Edge Native Workloads paths: /status: get: @@ -3041,7 +3041,7 @@ paths: summary: Login operationId: login description: > - CLI login (potctl). The `email` field is the login identifier (username or email); + CLI login. The `email` field is the login identifier (username or email); bootstrap admin may use a non-email username. User creation via POST /users still requires a valid email address. diff --git a/scripts/util.js b/scripts/util.js index f6cef3e5..c81b47a3 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -23,7 +23,7 @@ const DEV_DB_BACKUP = `${TEMP_DIR}/dev_database.sqlite` const PROD_DB = `${ROOT_DIR}/src/data/sqlite_files/prod_database.sqlite` const PROD_DB_BACKUP = `${TEMP_DIR}/prod_database.sqlite` -let dbName = process.env.DB_NAME || 'pot-controller' +let dbName = process.env.DB_NAME || 'iofog-controller' if (!dbName.endsWith('.sqlite')) { dbName += '.sqlite' } From 4ac14a4ddd8ffc45ba2950f2fcc5fa6bd5f4662d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 04:05:57 +0300 Subject: [PATCH 67/75] Export getAppLabelKey for Kubernetes service label helpers. Wire the flavor helper through services-service so standard lint passes and K8s router services get the correct app.kubernetes.io/name label. --- src/config/flavor.js | 3 ++- src/services/services-service.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/config/flavor.js b/src/config/flavor.js index 163edb26..615473bf 100644 --- a/src/config/flavor.js +++ b/src/config/flavor.js @@ -30,5 +30,6 @@ module.exports = { getRbacApiVersion, getControllerDistribution, getServiceAnnotationTag, - getComponentLabelKey + getComponentLabelKey, + getAppLabelKey } diff --git a/src/services/services-service.js b/src/services/services-service.js index 3c8a14ec..b249a71b 100644 --- a/src/services/services-service.js +++ b/src/services/services-service.js @@ -18,7 +18,7 @@ const { ensureSystemApplication, getSystemMicroserviceName } = require('../helpers/system-naming') -const { getServiceAnnotationTag, getComponentLabelKey } = require('../config/flavor') +const { getServiceAnnotationTag, getComponentLabelKey, getAppLabelKey } = require('../config/flavor') // const { Op } = require('sequelize') const K8S_ROUTER_CONFIG_MAP = 'iofog-router' From 862e6e9b261982779560e8e5c372150c1cfd8192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 04:06:01 +0300 Subject: [PATCH 68/75] Document v3.8.0 breaking changes and greenfield upgrade notice. Cover Edgelet-only agent runtime, API renames, removed endpoints, OIDC migration, EdgeOps Console embed, container-only distribution, and DB reset. --- CHANGELOG.md | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 829710c4..b92bddfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,97 @@ # Changelog +## [v3.8.0] - 2026-06-12 + +Controller v3.8 is a **greenfield** release aligned with **Edgelet**. There is **no upgrade path** from v3.7: use a fresh database and redeploy Controller + Edgelet together. + +### Breaking changes + +#### Agent runtime + +- **Edgelet only** — v3.7 legacy field agents are **not supported**. +- Requires **Edgelet v1.0.0-beta.1+** on the same release train (pin e.g. `v1.0.0-beta.2` with Controller `v3.8.0`). +- Provision accepts `containerEngine`: `edgelet` | `docker` | `podman` (was docker-implied). +- Agent config: `dockerUrl` → **`containerEngineUrl`**; `dockerPruningFrequency` → **`pruningFrequency`**. +- Agent architecture: `fogType` / `fogTypeId` → **`arch`** / **`archId`** (ids: 0=auto, 1=amd64, 2=arm64, 3=riscv64, 4=arm). +- Agent status: removed **`processedMessages`**, **`messageSpeed`**; added **`availableRuntimes`**, optional **`runtimeAgentPhase`**, **`controlPlaneQuiesced`**. +- Default container registry: **`docker.io`** (was `registry.hub.docker.com`). +- Reserved ports: **54321**, **54322**, **53**. +- New field-agent endpoint: **`POST /api/v3/agent/controller/register`** (system fogs only; Edgelet beta.1+). + +#### API — architectures and applications + +- **`GET /api/v3/fog-types`** → **`GET /api/v3/architectures/`** (public). +- **`/api/v3/flow/*`** → **`/api/v3/application/*`**; RBAC resource **`flows`** → **`applications`**. +- Microservice create: **`application`** string (name) in body — **`flowId` query param removed**. +- Error codes: **`INVALID_FLOW_*`** → **`INVALID_APPLICATION_*`**. +- Catalog and microservice images: **`images[]`** with `{ containerImage, archId }` (up to **4** per arch 1–4); single-image-only create removed. +- Microservice **`runtime`** must be in agent **`availableRuntimes`**. +- Service account volume type **`serviceAccount`** (immutable); `roleRef.apiGroup` **`edgelet.iofog.org/v1`** (was `agent.datasance.com/v3`). +- System microservice **`controller`** in application `system-{agentName}`; user delete → **403**, user PATCH → **400**. +- TCP bridge connector hosts: **`{appName}.{microserviceName}`** or **`edgelet.default.bridge.local`** (removed `iofog`, `iofog_{uuid}`). + +#### Removed APIs + +- **EdgeResource** APIs, models, and RBAC. +- **Diagnostics**, **strace**, image **snapshot** / **download** APIs. +- All **`/api/v3/flow`** routes. + +#### Authentication + +- **`keycloak-connect` removed** — generic OIDC (`openid-client` + JWKS discovery). +- Keycloak-specific env removed: **`KC_*`**, **`auth.realm`**, **`auth.realmKey`**, realm public key. +- Canonical OIDC env: **`OIDC_ISSUER_URL`** (full issuer URL), **`OIDC_CLIENT_ID`**, **`OIDC_CLIENT_SECRET`**, **`OIDC_CONSOLE_CLIENT_ID`**, **`AUTH_MODE`** (`embedded` | `external`). +- Embedded issuer at **`{CONTROLLER_PUBLIC_URL}/oidc`** when `AUTH_MODE=embedded`. +- TLS env renamed: **`SSL_*`** → **`TLS_*`**; use **`CONTROLLER_PUBLIC_URL`** + **`TRUST_PROXY`** behind reverse proxies. +- Browser login: OAuth BFF (`GET /api/v3/user/oauth/authorize`) — not browser `POST /user/login`. +- Bootstrap admin: **`OIDC_BOOTSTRAP_ADMIN_USERNAME`**, **`OIDC_BOOTSTRAP_ADMIN_PASSWORD`** (embedded first boot). + +#### EdgeOps Console (replaces ECN-Viewer) + +- **Container-only ship** — no npm publish of `@datasance/iofogcontroller` or `@datasance/ecn-viewer`. +- **EdgeOps Console** static SPA embedded in the Controller image (replaces **`@datasance/ecn-viewer`** / **`@iofog/ecn-viewer`** npm package). +- Build flavors: **`datasance`** | **`iofog`** via **`EDGEOPS_CONSOLE_FLAVOR`** / **`EDGEOPS_CONSOLE_VERSION`**. +- Env renames (no aliases): + + | Remove | Canonical | + |--------|-----------| + | `VIEWER_URL` | **`CONSOLE_URL`** | + | `VIEWER_PORT` | **`CONSOLE_PORT`** | + | `ECN_VIEWER_PATH` | **`EDGEOPS_CONSOLE_PATH`** | + | `OIDC_VIEWER_CLIENT_ID` | **`OIDC_CONSOLE_CLIENT_ID`** | + | `AUTH_VIEWER_CLIENT_ENABLED` | **`AUTH_CONSOLE_CLIENT_ENABLED`** | + +- Runtime **`controller-config.js`** uses **`consoleUrl`** (not `viewerUrl`); **`auth.*`** endpoints only — no `keycloak*` or `oidcIssuerUrl` keys in Console config. +- Dual-port default: API **51121**, Console **8008**. +- Status API field **`versions.ecnViewer`** retained for compatibility; value is the embedded Console version string. + +#### Database and distribution + +- **Greenfield schema** — **new install required**; no v3.7 → v3.8 database migrator. +- PKI: central router/NATS local CAs; legacy per-agent CAs migrated via one-time **rotation job** (Plan 5). +- **Node.js 24.x** required for dev and CI (was 16/18). +- Dual-mirror container images: **`ghcr.io/eclipse-iofog/controller`** and **`ghcr.io/datasance/controller`** from the **same commit SHA**; publish on **`v*` tags only** via repo variable **`IMAGE_REGISTRY`**. + +### Added + +- Embedded OIDC identity service with TOTP MFA (mandatory for `admin` group). +- **`POST /api/v3/auth/migration/export`** — one-way embedded → external IdP migration. +- **`POST /api/v3/auth/jwks/rotate`** — manual JWKS rotation (embedded mode). +- Built-in rate limiting on auth endpoints. +- HA BFF session store support for multi-replica Controller deployments. +- **NOTICE** file replaces per-file copyright headers. +- Neutral in-tree identity: RBAC **`iofog.org/v3`**, default namespace **`iofog`**, `package.json` name **`controller`**. + +### Removed + +- v3.7 legacy field-agent wire protocol and deprecated agent field names. +- npm package distribution of Controller and ECN-Viewer. +- EdgeResource, diagnostics, strace, and legacy flow APIs. +- Keycloak-specific configuration and `keycloak-connect` dependency. +- `processedMessages`, `messageSpeed`, and dual-read aliases for deprecated agent fields. + +--- + ## [v3.0.0] - 11-05-2022 ### Features From 016756965fc376976102afcb4823f2622f7e3a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 04:06:05 +0300 Subject: [PATCH 69/75] Add operator architecture overview for v3.8. Describe system context, module layout, agent contract summary, dual-port runtime, auth modes, and links to related operator documentation. --- docs/architecture.md | 232 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 docs/architecture.md diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 00000000..2dd75bf4 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,232 @@ +# Controller v3.8 — architecture overview + +**Audience:** Platform operators, integrators, and contributors +**Release:** v3.8.0 · Node **24.x** · **Edgelet only** (no v3.7 legacy field agent) + +Controller is the fleet control plane for ioFog / Datasance PoT. It orchestrates **Edgelet** nodes, deploys system microservices (router, NATS, controller), manages applications and workloads, issues certificates, and enforces RBAC for operator APIs. + +--- + +## System context + +```mermaid +flowchart TB + subgraph operators [Operators] + Console[EdgeOps Console SPA] + CLI[potctl / iofogctl / iofog-controller CLI] + end + + subgraph controller [Controller container] + API["REST + WebSocket API :51121"] + ConsoleSrv["EdgeOps Console static :8008"] + DB[(sqlite / mysql / postgres)] + API --> DB + end + + subgraph edge [Edge nodes] + EL[Edgelet field agent] + MS[Microservices: router, NATS, controller, workloads] + EL --> MS + end + + Console -->|Bearer JWT / OAuth BFF| API + CLI -->|Bearer JWT or password login| API + EL -->|Fog token /api/v3/agent/*| API + MS -.->|Skupper-style bridge| MS +``` + +| Component | Role | +|-----------|------| +| **Controller API** | User RBAC routes (`/api/v3/*`), agent wire protocol (`/api/v3/agent/*`), embedded OIDC issuer (`/oidc` when `AUTH_MODE=embedded`) | +| **EdgeOps Console** | Static SPA served from `EDGEOPS_CONSOLE_PATH`; reads `controller-config.js` written at startup | +| **Edgelet** | Edge runtime and field agent; polls Controller for config, microservices, and change flags | +| **System microservices** | **router** (Skupper-style messaging), **NATS** (MQTT leaf), **controller** (remote ControlPlane on system fogs) — deployed on agents by Edgelet | + +**Distribution:** container images only (`ghcr.io/eclipse-iofog/controller`, `ghcr.io/datasance/controller`). Same source tree on both mirrors; CI differs by `IMAGE_REGISTRY` only. + +--- + +## Runtime layout + +| Listener | Default port | Env override | Purpose | +|----------|--------------|--------------|---------| +| API | **51121** | `API_PORT` | REST, WebSocket exec/logs, OIDC routes | +| EdgeOps Console | **8008** | `CONSOLE_PORT` | Static operator UI | + +Startup sequence (`src/init.js` → `src/server.js`): + +1. Load config and OpenTelemetry. +2. Initialize vault (optional) and database. +3. Ensure central router/NATS local CAs. +4. Bootstrap embedded OIDC admin (embedded mode). +5. Register all routes from `src/routes/` and background jobs from `src/jobs/`. +6. Write `EDGEOPS_CONSOLE_PATH/controller-config.js` for the Console SPA. +7. Listen on API and Console ports (HTTP or TLS). + +--- + +## Source modules + +| Module | Path | Responsibility | +|--------|------|----------------| +| **Server** | `src/server.js`, `src/init.js`, `src/daemon.js` | Process entry, dual HTTP servers, middleware order, job scheduler | +| **Routes** | `src/routes/*.js` | Declarative HTTP/WebSocket route table (method, path, middleware) | +| **Controllers** | `src/controllers/*.js` | Thin handlers: validate input, call services, shape responses | +| **Services** | `src/services/*.js` | Business logic, change tracking, orchestration | +| **Schemas** | `src/schemas/*.js` | Request/response validation | +| **Data layer** | `src/data/models/`, `managers/`, `migrations/`, `seeders/` | Sequelize persistence (sqlite, mysql, postgres) | +| **Config** | `src/config/` | `config.yaml`, env mapping, OIDC, RBAC resource map, flavor | +| **RBAC** | `src/lib/rbac/`, `src/config/rbac-resources.yaml` | JWT subject extraction, route authorization | +| **Auth** | `src/config/oidc.js`, `src/services/auth-*` | Embedded/external OIDC, BFF, MFA, sessions | +| **Agent API** | `src/routes/agent.js`, `src/services/agent-service.js` | Edgelet field-agent wire protocol (fog token auth) | +| **WebSocket** | `src/websocket/` | Microservice exec and log streaming | +| **Jobs** | `src/jobs/*.js` | Periodic maintenance (status, tokens, NATS reconcile, cleanup) | +| **Certificates** | `src/services/certificate-service.js` | PKI for router/NATS and agent certs | +| **CLI** | `src/cli/` | `iofog-controller` administrative commands | +| **Vault** | `src/vault/` | Optional external secret backends | +| **API spec** | `docs/swagger.yaml` | Canonical OpenAPI for `/api/v3/*` | + +--- + +## API surfaces + +Controller exposes two authentication models on the same API port. + +### Operator API (OIDC Bearer + RBAC) + +Used by EdgeOps Console, `potctl`, `iofogctl`, and automation. + +| Area | Route prefix | Notes | +|------|--------------|-------| +| Status & architectures | `/api/v3/status`, `/api/v3/architectures/` | Public status; architecture catalog | +| Agents (ioFog) | `/api/v3/iofog` | Provision keys, agent CRUD, config | +| Applications | `/api/v3/application` | Replaces legacy `/api/v3/flow` | +| Microservices & catalog | `/api/v3/microservices`, `/api/v3/catalog` | Multi-arch `images[]`, service accounts | +| Networking | `/api/v3/router`, `/api/v3/nats`, `/api/v3/tunnel`, `/api/v3/service` | Router/NATS config, TCP bridge | +| RBAC | `/api/v3/roles`, `/api/v3/rolebindings`, … | Kubernetes-style roles | +| Auth & users | `/api/v3/user`, `/api/v3/auth` | Login, OAuth BFF, profile, JWKS rotation | +| Secrets & config | `/api/v3/secret`, `/api/v3/configMap`, `/api/v3/registries` | Fleet configuration | +| Certificates | `/api/v3/certificate` | PKI operations | +| WebSocket | `ws` routes in `src/routes/` | Exec and logs (Bearer token) | + +Browser login uses the OAuth BFF (`GET /api/v3/user/oauth/authorize`); CLI uses `POST /api/v3/user/login`. See [oidc-configuration.md](oidc-configuration.md), [external-oidc-client-setup.md](external-oidc-client-setup.md), and [rbac-reference.md](rbac-reference.md). + +### Agent API (fog token — no OIDC) + +Used by **Edgelet field agents** only. Paths under `/api/v3/agent/*` authenticate with the agent provisioning token, not user JWTs. + +| Command family | Examples | Purpose | +|----------------|----------|---------| +| Lifecycle | `POST /agent/provision`, `PUT /agent/status` | Register agent, heartbeat | +| Config | `GET/PATCH /agent/config` | Pull/push agent configuration | +| Workloads | `GET /agent/microservices`, `GET /agent/changes` | Reconcile microservices and change flags | +| ControlPlane | `POST /agent/controller/register` | System fog registers controller MS | +| Supporting | `GET /agent/version`, `GET /agent/volumeMounts`, registries, logs | OTA, mounts, diagnostics | + +Full request/response shapes: **`docs/swagger.yaml`** (agent paths). + +--- + +## Background jobs + +| Job | File | Role | +|-----|------|------| +| Fog status | `fog-status-job.js` | Mark agents offline when status pings lapse | +| Controller heartbeat | `controller-heartbeat-job.js` | Control-plane liveness | +| Fog token cleanup | `fog-token-cleanup-job.js` | Expire stale agent tokens | +| Controller cleanup | `controller-cleanup-job.js` | Orphaned controller MS housekeeping | +| Event cleanup | `event-cleanup-job.js` | Audit event retention | +| NATS reconcile | `nats-reconcile-worker-job.js` | NATS operator sync | +| Stopped app status | `stopped-app-status-job.js` | Application state maintenance | + +--- + +## Edgelet agent contract (summary) + +Controller v3.8 and Edgelet share a **frozen field-agent REST contract** on `/api/v3/agent/*`. The same release train must be deployed together (e.g. Controller `v3.8.0` + Edgelet `v1.0.0-beta.2`). Edgelet maintains the authoritative wire spec; Controller implements the server side. + +**Greenfield rules** + +- **Edgelet only** — v3.7 legacy field agents are unsupported. +- No read aliases for removed fields (`dockerUrl`, `fogType`, `messageSpeed`, etc.). +- Agent auth stays on **fog tokens**; OIDC applies to user routes only. + +**Provision** — `POST /api/v3/agent/provision` + +| Field | Semantics | +|-------|-----------| +| `key` | Provisioning key | +| `type` | Architecture code 0–4 (`archId`: auto, amd64, arm64, riscv64, arm) | +| `engine` | `edgelet` \| `docker` \| `podman` → stored as `containerEngine` | + +**Config pull** (`GET config`) — must return `containerEngineUrl`, `pruningFrequency`, `watchdogEnabled`, and fleet keys Edgelet expects. Do **not** return `dockerUrl` or `dockerPruningFrequency`. + +**Config push** (`PATCH config`) — Edgelet pushes agent-local overrides using canonical keys (`containerEngineUrl`, `pruningFrequency`, `networkInterface`, …). + +**Status** (`PUT status`) — required keys include `daemonStatus`, resource usage, `microserviceStatus`, `version`, `tunnelStatus`, and v3.8 additions: + +| Added (v3.8) | Removed (v3.8) | +|--------------|----------------| +| `availableRuntimes` | `processedMessages` | +| `runtimeAgentPhase` (optional) | `messageSpeed` | +| `controlPlaneQuiesced` (optional) | `microserviceMessageCounts` (do not persist) | + +**Change flags** — unchanged names: `deleteNode`, `reboot`, `config`, `version`, `registries`, `prune`, `volumeMounts`, `microserviceConfig`, `microserviceList`, `execSessions`, `tunnel`, `microserviceLogs`, `fogLogs`. + +**Microservice list** (`GET microservices`) — each entry includes derived flags `isRouter`, `isNats`, and DB-backed **`isController`** for the system controller microservice. + +**Controller MS register** — `POST /api/v3/agent/controller/register` (system fogs only, beta.1+ Edgelet): + +- Auth: agent fog token; `fog.isSystem === true`. +- Body: Edgelet-generated `uuid`, `images[]`, `registryId`, optional `ports`, `env`, `volumeMappings`. +- Server upserts MS with `isController: true` in application `system-{fogName}`; returns `{ "uuid": "..." }`. +- User `DELETE` / `PATCH` on controller MS → **403** / **400**; operator system PATCH allowed. + +**Volume mounts** — `GET volumeMounts` returns bind/volume shapes plus system-injected immutable `serviceAccount` entries. + +**Service account RBAC** — microservice roles use apiGroup **`edgelet.iofog.org/v1`**; SA/role changes trigger `microserviceList` change tracking. + +For the full bilateral contract (including ControlPlane env vars and verification references), see Edgelet documentation: + +- [Edgelet README / docs](https://github.com/eclipse-iofog/edgelet/blob/main/docs/edgelet/README.md) +- Edgelet mirror of this contract: `controller-invariants.md` in the [eclipse-iofog/edgelet](https://github.com/eclipse-iofog/edgelet) repository + +--- + +## Data and PKI + +| Topic | v3.8 behavior | +|-------|---------------| +| **Database** | Greenfield v3.8.0 schema — **new install only** (no v3.7 migrator). Supports sqlite (dev), mysql, postgres (production/HA). | +| **Applications** | Table `Applications` (was `Flows`); API identity by **name** string. | +| **Architectures** | Table `Architectures` (was `FogTypes`); `archId` 0–4. | +| **PKI** | Central **default-router-local-ca** and **default-nats-local-ca** for all new agents; no per-agent local CAs on provision (greenfield — no v3.7 PKI migration job). See [pki.md](pki.md). | +| **TCP bridge** | Connector hosts `{appName}.{microserviceName}` or `edgelet.default.bridge.local`; reserved ports **54321**, **54322**, **53**. | + +--- + +## Authentication modes + +| Mode | When | Issuer | +|------|------|--------| +| **Embedded** | `AUTH_MODE=embedded` | In-process OIDC at `{CONTROLLER_PUBLIC_URL}/oidc` | +| **External** | `AUTH_MODE=external` | Third-party IdP via `OIDC_ISSUER_URL` | + +Embedded mode bootstraps an admin from `OIDC_BOOTSTRAP_ADMIN_*` env vars on first start. External mode uses the OAuth BFF for browser login; CLI retains password + TOTP via `POST /api/v3/user/login`. Full env reference: [oidc-configuration.md](oidc-configuration.md). + +Agent routes and WebSocket exec/logs for agents are **outside** OIDC — see [rbac-reference.md](rbac-reference.md) for which user routes are public or agent-scoped. + +--- + +## Related operator docs + +| Document | Topic | +|----------|-------| +| [README.md](../README.md) | Install, quick start, Edgelet pin | +| [CHANGELOG.md](../CHANGELOG.md) | v3.8.0 breaking changes | +| [swagger.yaml](swagger.yaml) | HTTP API reference | +| [rbac-reference.md](rbac-reference.md) | Roles, bindings, route map | +| [pki.md](pki.md) | Central CAs, cert renewal, NATS operator rotation | +| [oidc-configuration.md](oidc-configuration.md) | Embedded/external auth modes and env vars | +| [external-oidc-client-setup.md](external-oidc-client-setup.md) | External IdP client configuration | +| [CONTRIBUTING](../CONTRIBUTING) | Dual-mirror CI and development | From 197aa1bfbfcbe900314435ff7d48e4d7bd029ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 04:06:09 +0300 Subject: [PATCH 70/75] Add operator guides for PKI and OIDC configuration. Document central CAs, cert renewal, NATS rotation, and embedded vs external auth modes with full environment variable reference. --- docs/oidc-configuration.md | 204 +++++++++++++++++++++++++++++++++++++ docs/pki.md | 120 ++++++++++++++++++++++ 2 files changed, 324 insertions(+) create mode 100644 docs/oidc-configuration.md create mode 100644 docs/pki.md diff --git a/docs/oidc-configuration.md b/docs/oidc-configuration.md new file mode 100644 index 00000000..2542b31f --- /dev/null +++ b/docs/oidc-configuration.md @@ -0,0 +1,204 @@ +# Controller OIDC — operator configuration + +**Audience:** Platform operators and IdP administrators +**Release:** v3.8.0 + +Controller v3.8 replaces Keycloak-specific integration with **generic OIDC**. User routes and operator WebSockets require Bearer JWTs validated via JWKS discovery. **Agent routes** (`/api/v3/agent/*`) keep fog-token auth — OIDC does not apply to field agents. + +--- + +## Auth modes + +| Mode | Env | Issuer | Typical use | +|------|-----|--------|-------------| +| **Embedded** | `AUTH_MODE=embedded` | `{CONTROLLER_PUBLIC_URL}/oidc` (in-process) | Single-node or HA with mysql/postgres; built-in users and MFA | +| **External** | `AUTH_MODE=external` | `OIDC_ISSUER_URL` (third-party IdP) | Enterprise IdP (Keycloak, Auth0, Okta, Azure AD, …) | + +Set **`CONTROLLER_PUBLIC_URL`** to the URL clients use to reach Controller (HTTPS in production). Behind a reverse proxy, also set **`TRUST_PROXY=true`** so redirects and issuer URLs honor `X-Forwarded-*` headers. + +Development with `http://localhost:*` requires **`AUTH_INSECURE_ALLOW_HTTP=true`**. + +--- + +## Environment variables + +Canonical names map to `config.yaml` under `auth.*` (see `src/config/env-mapping.js`). + +### Core + +| Variable | Mode | Required | Purpose | +|----------|------|----------|---------| +| `AUTH_MODE` | both | Yes | `embedded` or `external` | +| `CONTROLLER_PUBLIC_URL` | both | Yes | External URL; embedded issuer base | +| `TRUST_PROXY` | both | When proxied | Honor forwarded headers | +| `OIDC_ISSUER_URL` | external | Yes | Full issuer URL (e.g. `https://auth.example.com/realms/myrealm`) | +| `OIDC_CLIENT_ID` | both | Yes | Confidential API client (default embedded: `controller`) | +| `OIDC_CLIENT_SECRET` | both | external required; embedded optional | Client secret for token exchange | +| `CONSOLE_URL` | both | Recommended | EdgeOps Console browser origin (defaults to public URL) | +| `CONSOLE_PORT` | both | Optional | Console listener (default **8008**) | + +### Embedded bootstrap (first install) + +| Variable | Purpose | +|----------|---------| +| `OIDC_BOOTSTRAP_ADMIN_USERNAME` | Initial admin login name | +| `OIDC_BOOTSTRAP_ADMIN_PASSWORD` | Initial admin password | +| `AUTH_INSECURE_ALLOW_BOOTSTRAP_LOG` | Log bootstrap creds once (dev only; default off) | + +Bootstrap runs on startup when no admin exists, or **reconciles** when env credentials change (greenfield — old bootstrap user is replaced). + +### Optional Console public client + +| Variable | Purpose | +|----------|---------| +| `OIDC_CONSOLE_CLIENT_ID` | Separate public OIDC client for future SPA-direct flows | +| `AUTH_CONSOLE_CLIENT_ENABLED` | Enable Console client registration (default off) | + +Primary browser login uses the Controller OAuth BFF, not a separate Console OIDC client. + +### Rate limiting and sessions (OAuth BFF) + +| Variable | Default | Purpose | +|----------|---------|---------| +| `AUTH_RATE_LIMIT_ENABLED` | `true` | Rate limit auth endpoints | +| `AUTH_RATE_LIMIT_MAX_REQUESTS` | `60` | Requests per window per IP | +| `AUTH_RATE_LIMIT_WINDOW_MS` | `60000` | Sliding window (ms) | +| `AUTH_SESSION_STORE_TYPE` | `memory` | `memory` or `database` (auto database for mysql/postgres) | +| `AUTH_SESSION_STORE_TTL_MS` | `600000` | BFF OAuth session TTL | +| `AUTH_SESSION_SECRET` | auto | Session cookie signing secret | + +For HA Controller replicas with external IdP browser login, use **`AUTH_SESSION_STORE_TYPE=database`** (mysql/postgres) so OAuth `state` survives any replica. + +### Token TTL overrides (embedded) + +| Variable | Maps to | +|----------|---------| +| `AUTH_ACCESS_TOKEN_TTL_SECONDS` | Access token lifetime | +| `AUTH_REFRESH_TOKEN_TTL_SECONDS` | Refresh token lifetime | +| `AUTH_OIDC_INTERACTION_TTL_SECONDS` | Embedded interaction UI TTL | +| `AUTH_OIDC_GRANT_TTL_SECONDS` | Grant record TTL | +| `AUTH_OIDC_SESSION_TTL_SECONDS` | OIDC provider session TTL | +| `AUTH_OIDC_ID_TOKEN_TTL_SECONDS` | ID token TTL | + +### Removed (do not set) + +| Legacy | Replacement | +|--------|-------------| +| `KC_URL`, `KC_REALM`, `KC_CLIENT_ID`, `KC_CLIENT_SECRET` | `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET` | +| `auth.realm`, `auth.realmKey` | JWKS via issuer discovery | +| `OIDC_VIEWER_CLIENT_ID` | `OIDC_CONSOLE_CLIENT_ID` | +| `SSL_*` | `TLS_*` | + +--- + +## Login flows + +### Browser (EdgeOps Console) + +Both modes use the **OAuth BFF** — the Console must not POST passwords from the browser. + +1. User clicks Sign in → `GET /api/v3/user/oauth/authorize` +2. **External:** redirect to IdP (authorization code + PKCE S256) +3. **Embedded:** redirect to embedded interaction UI at `{CONSOLE_URL}/login/oauth?interaction=` +4. Callback: `GET /api/v3/user/oauth/callback` +5. Tokens delivered to `{CONSOLE_URL}/login#accessToken=...&refreshToken=...` + +Register this redirect URI at the external IdP: + +```text +{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback +``` + +At startup Controller writes `EDGEOPS_CONSOLE_PATH/controller-config.js` with `consoleUrl` and relative auth endpoint paths (no `keycloak*` keys). + +### CLI (potctl / automation) + +```http +POST /api/v3/user/login +Content-Type: application/json + +{ "email": "", "password": "", "totp": "" } +``` + +- **Embedded:** Controller validates against local users; MFA required for `admin` group. +- **External:** password grant against the IdP (IdP must allow direct access grants if CLI login is used). + +Refresh: `POST /api/v3/user/refresh` with `{ "refreshToken": "..." }`. + +--- + +## RBAC and JWT claims + +After login, API calls use `Authorization: Bearer `. Controller resolves RBAC **Group** subjects from the token (in order): + +1. `resource_access[{OIDC_CLIENT_ID}].roles` +2. Top-level `roles` array +3. `groups` array (requires `groups` scope on external IdP) + +**User** subject: `preferred_username` → `username` → `email` → `sub`. + +External IdP setup (scopes, PKCE, redirect URIs, Keycloak checklist): [external-oidc-client-setup.md](external-oidc-client-setup.md). + +--- + +## Embedded-only admin APIs + +| Endpoint | Purpose | +|----------|---------| +| `POST /api/v3/auth/jwks/rotate` | Rotate embedded OIDC signing keys (manual) | +| `POST /api/v3/auth/migration/export` | Export users/groups for one-way embedded → external migration | +| `GET/POST /api/v3/users` | Embedded user administration | + +### JWKS rotation (embedded) + +1. Admin Bearer → `POST /api/v3/auth/jwks/rotate` +2. Response includes `{ kid, rotatedAt, restartRequired: true }` +3. **Restart Controller** so the embedded issuer reloads signing material +4. Existing access tokens remain valid until expiry — plan during a maintenance window + +### Migrate embedded → external + +1. Export: `POST /api/v3/auth/migration/export` (no password hashes) +2. Provision users and groups in the external IdP using exported emails and group names +3. Switch env to `AUTH_MODE=external` with `OIDC_ISSUER_URL` and client credentials +4. **External → embedded is not supported** + +--- + +## HA notes + +| Concern | Guidance | +|---------|----------| +| Embedded issuer + sqlite | Single replica only | +| Embedded issuer + mysql/postgres | Multiple replicas; shared DB | +| OAuth BFF sessions | Use `AUTH_SESSION_STORE_TYPE=database` for multi-replica external browser login | +| JWKS cache | Restart all replicas after embedded JWKS rotation | + +--- + +## Verification + +### Embedded quick check + +```bash +curl -sS -X POST "http://localhost:51121/api/v3/user/login" \ + -H 'Content-Type: application/json' \ + -d '{"email":"","password":"","totp":""}' +``` + +Open Console at `http://localhost:8008` → Sign in → complete MFA if prompted. + +### External quick check + +See [external-oidc-client-setup.md](external-oidc-client-setup.md) § Verification. + +--- + +## Related docs + +| Document | Topic | +|----------|-------| +| [external-oidc-client-setup.md](external-oidc-client-setup.md) | External IdP client, scopes, Keycloak checklist | +| [rbac-reference.md](rbac-reference.md) | Roles, bindings, public routes | +| [architecture.md](architecture.md) | Auth modes summary, API surfaces | +| [swagger.yaml](swagger.yaml) | User and auth admin endpoints | diff --git a/docs/pki.md b/docs/pki.md new file mode 100644 index 00000000..34983c1c --- /dev/null +++ b/docs/pki.md @@ -0,0 +1,120 @@ +# Controller PKI — operator guide + +**Audience:** Platform operators +**Release:** v3.8.0 greenfield + +Controller issues and stores TLS material for router messaging, NATS MQTT, and inter-site links. v3.8 uses **central local CAs** shared across the fleet instead of per-agent CA secrets. + +--- + +## Central certificate authorities + +On startup, Controller ensures two fleet-wide local CAs (stored as TLS secrets): + +| Secret name | Signs | +|-------------|--------| +| `default-router-local-ca` | Router **messaging** certs (`router-local-server-*`, `router-local-agent-*`) | +| `default-nats-local-ca` | NATS **MQTT local** certs (`nats-mqtt-*`) | + +Site CAs (unchanged from earlier releases): + +| Secret name | Purpose | +|-------------|---------| +| `router-site-ca` | Inter-router (site) TLS | +| `nats-site-ca` | Inter-NATS (site) TLS | + +**Greenfield v3.8:** new agent provisions always receive certs signed by the central local CAs. Controller does **not** create `router-local-ca-{agentName}` or `nats-local-ca-{token}` secrets on provision. + +Per-agent local CA names may still appear in **delete cleanup** for orphaned legacy artifacts; they are not part of the v3.8 provision path. + +--- + +## Greenfield install (no PKI migration job) + +v3.8 is a **new install only** release (no v3.7 → v3.8 database migrator). + +Plan 5 originally scoped a **one-time PKI rotation job** to re-sign certs from legacy per-agent CAs under the central CAs. That job was **not implemented** — greenfield policy means labs and production deploy fresh Controller + Edgelet fleets without carrying forward v3.7 secrets. + +| Scenario | Operator action | +|----------|-----------------| +| New v3.8 fleet | Install Controller v3.8; central CAs are created at boot; provision Edgelet agents normally | +| Old v3.7 lab with per-agent CAs | **Wipe and reinstall** (new DB + new secrets) per greenfield policy — do not attempt in-place PKI migration | +| Agent host / IP change | Controller recreates affected router/NATS certs and sets the **`volumeMounts`** change flag so Edgelet reloads mounted secrets (automatic) | + +--- + +## Automatic operations + +### Boot + +After database init, Controller calls `ensureCentralLocalCAs()` — creating `default-router-local-ca` and `default-nats-local-ca` if missing (60-month CA validity for central CAs). + +### Agent provision and host changes + +When an agent is provisioned or its **host** changes, Controller: + +1. Ensures the central local CA exists. +2. Creates or recreates router local-server / local-agent and NATS MQTT certs with current DNS SANs (including bridge SANs such as `router.default.svc.bridge.local`). +3. Sets **`volumeMounts`** (and related) change tracking so Edgelet picks up new secret mounts. + +No manual PKI step is required for normal agent lifecycle. + +--- + +## Manual certificate renewal + +Operators can renew individual TLS secrets before expiry: + +```http +POST /api/v3/certificates/{name}/renew +Authorization: Bearer +``` + +Renewal generates a new key pair and secret data, preserving the signing CA relationship. If the signing CA is expired, renew the CA first. + +List certs nearing expiry (default window 30 days): + +```http +GET /api/v3/certificates/expiring?days=30 +Authorization: Bearer +``` + +After renewing certs mounted into running microservices, trigger agent reconciliation (host update or wait for change flags) so Edgelet reloads updated secrets. + +See **`docs/swagger.yaml`** for full certificate API (`/api/v3/certificates/*`, `/api/v3/certificates/ca/*`). + +--- + +## NATS operator rotation + +NATS account JWTs are signed by a fleet **NATS operator** key. To rotate the operator and re-sign all accounts: + +```http +POST /api/v3/nats/operator/rotate +Authorization: Bearer +``` + +Controller accepts the request, rotates operator material, re-signs accounts, and schedules resolver reconciliation in the background. Plan maintenance when NATS leaf nodes may reload operator trust. + +This is **NATS credential rotation**, not the skipped Plan 5 per-agent CA migration. + +--- + +## DNS SANs and TCP bridge + +Router and NATS MQTT certs include bridge and cluster SANs required by Edgelet networking: + +- `router.default.svc.bridge.local`, `nats.default.svc.bridge.local` +- Default router: `router.{namespace}.svc.cluster.local` when applicable + +TCP bridge connector hostnames for services are documented in [architecture.md](architecture.md) (reserved ports **54321**, **54322**, **53**). + +--- + +## Related docs + +| Document | Topic | +|----------|-------| +| [architecture.md](architecture.md) | Module layout, agent contract, PKI summary | +| [swagger.yaml](swagger.yaml) | Certificate and NATS operator API reference | +| [CHANGELOG.md](../CHANGELOG.md) | v3.8.0 breaking changes | From 3bce87dcf9de49717fe068f2e6ff6d86ebda1f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 04:06:14 +0300 Subject: [PATCH 71/75] Rewrite README for v3.8 container distribution and Edgelet requirement. Add CI and release badges, dual-mirror quick start, Edgelet install examples, platform CLI links, local dev steps, and documentation index. --- README.md | 136 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 117 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 331e4bcc..d997b9c8 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,131 @@ -# ioFog Controller - -See [CONTRIBUTING](CONTRIBUTING) for the dual-mirror repository model and CI workflow. Full operator-facing README updates are planned for the v3.8 release (Plan 13). - -### Status - -![](https://img.shields.io/github/release/datasance/controller.svg?style=flat) - -![](https://img.shields.io/github/repo-size/datasance/controller.svg?style=flat) -![](https://img.shields.io/github/last-commit/datasance/controller.svg?style=flat) -![](https://img.shields.io/github/contributors/datasance/controller.svg?style=flat) -![](https://img.shields.io/github/issues/datasance/controller.svg?style=flat) +# Controller +[![CI](https://github.com/eclipse-iofog/Controller/actions/workflows/ci.yaml/badge.svg)](https://github.com/eclipse-iofog/Controller/actions/workflows/ci.yaml) +[![Release](https://img.shields.io/github/v/release/eclipse-iofog/Controller?include_prereleases)](https://github.com/eclipse-iofog/Controller/releases) +[![Node.js](https://img.shields.io/badge/node-24.x-brightgreen.svg)](https://nodejs.org/) +[![License](https://img.shields.io/badge/License-EPL--2.0-blue.svg)](LICENSE) +[![Ship](https://img.shields.io/badge/ship-container-blue.svg)](Dockerfile) ![Supports amd64 Architecture][amd64-shield] ![Supports aarch64 Architecture][arm64-shield] [arm64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg [amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg -[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) +**Upstream:** [eclipse-iofog/Controller](https://github.com/eclipse-iofog/Controller) · **Datasance mirror:** [Datasance/Controller](https://github.com/Datasance/Controller) + +**Cloud-native control plane for edge fleets.** Controller orchestrates [Edgelet](https://github.com/eclipse-iofog/edgelet) nodes, microservices, routing, NATS messaging, RBAC, and certificates. v3.8 is a **greenfield** release: **Edgelet only** — v3.7 legacy field agents are not supported. + +See [CONTRIBUTING](CONTRIBUTING) for the dual-mirror repository model, CI workflow, and per-mirror GitHub Actions variables. -## Install +## Platforms -The entire Datasance PoT platform is best deployed through the unified CLI: `potctl`. +| Artifact | amd64 | arm64 | Notes | +|----------|-------|-------|-------| +| Container image | yes | yes | Primary distribution for v3.8 | +| Local dev (Node 24) | yes | yes | `npm run start-dev` | -Go to [Datasance Docs](https://docs.datasance.com) to learn how to deploy the ioFog Control Plane and Agents. +## Quick start (container) -## Usage +Pull a release image from the registry that matches your product line, then run Controller with the API on **51121** and EdgeOps Console on **8008**: + +### Eclipse ioFog + +```bash +docker run -d --name controller \ + -p 51121:51121 \ + -p 8008:8008 \ + ghcr.io/eclipse-iofog/controller:v3.8.0 ``` -iofog-controller + +### Datasance PoT + +```bash +docker run -d --name controller \ + -p 51121:51121 \ + -p 8008:8008 \ + ghcr.io/datasance/controller:v3.8.0 ``` -For full installation and usage, visit [Datasance Docs](https://docs.datasance.com). +Verify the API: + +```bash +curl -s http://localhost:51121/api/v3/status | head +``` + +Open EdgeOps Console at `http://localhost:8008`. For production, set `CONTROLLER_PUBLIC_URL`, TLS, and an external database (mysql/postgres) — see [Documentation](#documentation) below. + +Images publish to `${IMAGE_REGISTRY}/controller` on **`v*` tags only**; both mirrors build from the **same commit SHA**. See [CONTRIBUTING](CONTRIBUTING) for CI variables. + +## Edgelet (required agent) + +Controller v3.8 requires **Edgelet v1.0.0-beta.1+** on the same release train. Install Edgelet on each edge node before provisioning: + +| Channel | GitHub repo | Container image | +|---------|-------------|-----------------| +| **Eclipse (canonical)** | [eclipse-iofog/edgelet](https://github.com/eclipse-iofog/edgelet) | `ghcr.io/eclipse-iofog/edgelet:` | +| **Datasance mirror** | [Datasance/edgelet](https://github.com/Datasance/edgelet) | `ghcr.io/datasance/edgelet:` | + +**Pin:** use an Edgelet release tag that matches your Controller version (e.g. **`v1.0.0-beta.2`** with Controller **`v3.8.0`**). Identical builds and tags on both mirrors. + +### Eclipse (canonical) + +```bash +curl -fsSL https://github.com/eclipse-iofog/edgelet/releases/download/v1.0.0-beta.2/install.sh -o install.sh +chmod +x install.sh +sudo ./install.sh --version=v1.0.0-beta.2 +edgelet config --a http://:51121/api/v3/ +edgelet provision +``` + +### Datasance mirror + +```bash +curl -fsSL https://github.com/Datasance/edgelet/releases/download/v1.0.0-beta.2/install.sh -o install.sh +chmod +x install.sh +sudo ./install.sh --version=v1.0.0-beta.2 +edgelet config --a http://:51121/api/v3/ +edgelet provision +``` + +Edgelet docs: [eclipse-iofog/edgelet](https://github.com/eclipse-iofog/edgelet/blob/main/docs/edgelet/README.md) + +## Full platform install + +For production ECN / PoT deployments, use the unified platform CLI for your product line: + +| Product | CLI | Documentation | +|---------|-----|---------------| +| Eclipse ioFog | [iofogctl](https://github.com/eclipse-iofog/iofogctl) | [Eclipse ioFog docs](https://docs.iofog.org/) | +| Datasance PoT | [potctl](https://github.com/Datasance/potctl) | [Datasance docs](https://docs.datasance.com/) | + +## CLI + +The container and npm package expose the **`iofog-controller`** CLI: + +```bash +iofog-controller [options] +iofog-controller --help +``` + +## Local development + +Requires **Node.js 24.x** (`nvm use 24`): + +```bash +npm ci --legacy-peer-deps +npm run dev:embedded +``` + +API: `http://localhost:51121` · Console (embedded or split): set `CONSOLE_URL` when running EdgeOps Console separately. + +## Documentation + +| Topic | Doc | +|-------|-----| +| Architecture | [docs/architecture.md](docs/architecture.md) | +| RBAC | [docs/rbac-reference.md](docs/rbac-reference.md) | +| External OIDC | [docs/external-oidc-client-setup.md](docs/external-oidc-client-setup.md) | + +## License + +[EPL-2.0](LICENSE) — see [NOTICE](NOTICE) for attribution. From ea0512a5f85d2d32302f574bf9ff94e6c78cf138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 04:06:20 +0300 Subject: [PATCH 72/75] Scrub internal references from public docs and fix agent disk default. Point RBAC maintenance at npm scripts instead of workspace paths and align swagger diskDirectory default with the Edgelet data directory. --- docs/rbac-reference.md | 4 ++-- docs/swagger.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/rbac-reference.md b/docs/rbac-reference.md index e324ad13..dfe92555 100644 --- a/docs/rbac-reference.md +++ b/docs/rbac-reference.md @@ -184,7 +184,7 @@ Optional CI wiring is planned for Plan 11. | System roles | `src/config/rbac-system-roles.js` | | RBAC middleware | `src/lib/rbac/middleware.js` | | Authorizer | `src/lib/rbac/authorizer.js` | -| Route inventory (generated) | `.cursor/controllerv3.8/docs/09-rbac-audit-route-inventory.md` | -| Drift script | `scripts/rbac-audit.js`, `scripts/route-inventory.js` | +| Route inventory (generated) | `node scripts/route-inventory.js` | +| Drift script | `scripts/rbac-audit.js`, `npm run rbac-audit` | | External IdP groups | `docs/external-oidc-client-setup.md` | | HTTP API spec | `docs/swagger.yaml` | diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 381347ae..e071aecf 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -7396,7 +7396,7 @@ components: default: 50 diskDirectory: type: string - default: /var/lib/iofog-agent + default: /var/lib/edgelet/ memoryLimit: type: number default: 4096 From df215cd5a9cf3a5c4bc61dc163116a49d8f50f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 04:06:41 +0300 Subject: [PATCH 73/75] swagger --- docs/swagger.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/swagger.json b/docs/swagger.json index 991d6f19..665490c5 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "version": "1.0.0", + "version": "3.8.0", "title": "Cloud Natice Controller for Edge Native Workloads" }, "tags": [ From 03b624a84f5621c27b3f275d2aaeca0f11dcb96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 15:28:21 +0300 Subject: [PATCH 74/75] Add optional semver to fog version commands. Users can POST an optional semver in the request body when setting upgrade or rollback. The value is stored on FogVersionCommands and returned to the agent only when set. --- src/controllers/iofog-controller.js | 4 +++ src/data/models/fogversioncommand.js | 4 +++ src/schemas/iofog.js | 5 +++- src/services/agent-service.js | 8 ++++- src/services/iofog-service.js | 12 ++++++-- test/src/controllers/iofog-controller.test.js | 22 ++++++++++++++ test/src/services/agent-service.test.js | 29 +++++++++++++++++++ test/src/services/iofog-service.test.js | 19 ++++++++++-- 8 files changed, 96 insertions(+), 7 deletions(-) diff --git a/src/controllers/iofog-controller.js b/src/controllers/iofog-controller.js index 13d62f5e..4550cdb8 100644 --- a/src/controllers/iofog-controller.js +++ b/src/controllers/iofog-controller.js @@ -48,6 +48,10 @@ async function setFogVersionCommandEndPoint (req) { versionCommand: req.params.versionCommand } + if (req.body && Object.hasOwn(req.body, 'semver')) { + fogVersionCommand.semver = req.body.semver + } + return FogService.setFogVersionCommandEndPoint(fogVersionCommand, false) } diff --git a/src/data/models/fogversioncommand.js b/src/data/models/fogversioncommand.js index f03d422d..65765164 100644 --- a/src/data/models/fogversioncommand.js +++ b/src/data/models/fogversioncommand.js @@ -11,6 +11,10 @@ module.exports = (sequelize, DataTypes) => { /* eslint-disable new-cap */ type: DataTypes.STRING(100), field: 'version_command' + }, + semver: { + type: DataTypes.STRING(100), + field: 'semver' } }, { tableName: 'FogVersionCommands', diff --git a/src/schemas/iofog.js b/src/schemas/iofog.js index 91f0ce48..3a3bc130 100644 --- a/src/schemas/iofog.js +++ b/src/schemas/iofog.js @@ -1,3 +1,5 @@ +const { versionRegex } = require('./utils/utils') + const iofogCreate = { id: '/iofogCreate', type: 'object', @@ -193,7 +195,8 @@ const iofogSetVersionCommand = { type: 'object', properties: { uuid: { type: 'string' }, - versionCommand: { enum: ['upgrade', 'rollback'] } + versionCommand: { enum: ['upgrade', 'rollback'] }, + semver: { type: 'string', pattern: versionRegex } }, required: ['uuid', 'versionCommand'], additionalProperties: true diff --git a/src/services/agent-service.js b/src/services/agent-service.js index fac63cc4..cd1b82b5 100644 --- a/src/services/agent-service.js +++ b/src/services/agent-service.js @@ -538,11 +538,17 @@ const getAgentChangeVersionCommand = async function (fog, transaction) { iofogUuid: fog.uuid }, transaction) - return { + const response = { versionCommand: versionCommand.versionCommand, provisionKey: provision.provisionKey, expirationTime: provision.expirationTime } + + if (versionCommand.semver) { + response.semver = versionCommand.semver + } + + return response } const updateHalHardwareInfo = async function (hardwareData, fog, transaction) { diff --git a/src/services/iofog-service.js b/src/services/iofog-service.js index 02ae548f..04c105e5 100644 --- a/src/services/iofog-service.js +++ b/src/services/iofog-service.js @@ -1142,13 +1142,21 @@ async function generateProvisioningKeyEndPoint (fogData, isCLI, transaction) { } async function setFogVersionCommandEndPoint (fogVersionData, isCLI, transaction) { - await Validator.validate(fogVersionData, Validator.schemas.iofogSetVersionCommand) + const validationData = { + uuid: fogVersionData.uuid, + versionCommand: fogVersionData.versionCommand + } + if (fogVersionData.semver != null) { + validationData.semver = fogVersionData.semver + } + await Validator.validate(validationData, Validator.schemas.iofogSetVersionCommand) const queryFogData = { uuid: fogVersionData.uuid } const newVersionCommand = { iofogUuid: fogVersionData.uuid, - versionCommand: fogVersionData.versionCommand + versionCommand: fogVersionData.versionCommand, + semver: fogVersionData.semver ?? null } const fog = await FogManager.findOne(queryFogData, transaction) diff --git a/test/src/controllers/iofog-controller.test.js b/test/src/controllers/iofog-controller.test.js index 7172abe5..18721235 100644 --- a/test/src/controllers/iofog-controller.test.js +++ b/test/src/controllers/iofog-controller.test.js @@ -398,6 +398,7 @@ describe('ioFog Controller', () => { uuid: $uuid, versionCommand: $versionCommand, }, + body: {}, })) def('response', () => Promise.resolve()) def('subject', () => $subject.setFogVersionCommandEndPoint($req)) @@ -414,6 +415,27 @@ describe('ioFog Controller', () => { }, false) }) + context('when semver is provided in body', () => { + def('req', () => ({ + params: { + uuid: $uuid, + versionCommand: $versionCommand, + }, + body: { + semver: '3.2.0', + }, + })) + + it('passes semver to service', async () => { + await $subject + expect(ioFogService.setFogVersionCommandEndPoint).to.have.been.calledWith({ + uuid: $uuid, + versionCommand: $versionCommand, + semver: '3.2.0', + }, false) + }) + }) + context('when ioFogService#setFogVersionCommandEndPoint fails', () => { const error = 'Error!' diff --git a/test/src/services/agent-service.test.js b/test/src/services/agent-service.test.js index e1955aa0..a4491767 100644 --- a/test/src/services/agent-service.test.js +++ b/test/src/services/agent-service.test.js @@ -1478,6 +1478,35 @@ describe('Agent Service', () => { }) }) }) + + context('when semver is set on version command', () => { + def('semver', () => '3.2.0') + def('versionCommand', () => ({ + versionCommand: $versionCommandLine, + semver: $semver, + })) + def('response', () => ({ + versionCommand: $versionCommandLine, + provisionKey: $provisionKey, + expirationTime: $expirationTime, + semver: $semver, + })) + + it('includes semver in response', () => { + return expect($subject).to.eventually.eql($response) + }) + }) + + context('when semver is null on version command', () => { + def('versionCommand', () => ({ + versionCommand: $versionCommandLine, + semver: null, + })) + + it('omits semver from response', () => { + return expect($subject).to.eventually.eql($response) + }) + }) }) describe('.updateHalHardwareInfo()', () => { diff --git a/test/src/services/iofog-service.test.js b/test/src/services/iofog-service.test.js index 08406a18..e60b8768 100644 --- a/test/src/services/iofog-service.test.js +++ b/test/src/services/iofog-service.test.js @@ -436,9 +436,9 @@ describe('ioFog Service', () => { describe('.setFogVersionCommandEndPoint()', () => { const uuid = 'testUuid' - const fogVersionData = { uuid, versionCommand: 'upgrade' } - def('subject', () => $subject.setFogVersionCommandEndPoint(fogVersionData, isCLI, transaction)) + def('fogVersionData', () => ({ uuid, versionCommand: 'upgrade' })) + def('subject', () => $subject.setFogVersionCommandEndPoint($fogVersionData, isCLI, transaction)) beforeEach(() => { $sandbox.stub(Validator, 'validate').resolves(true) @@ -462,12 +462,25 @@ describe('ioFog Service', () => { expect(ioFogProvisionKeyManager.updateOrCreate).to.have.been.calledOnce expect(ioFogVersionCommandManager.updateOrCreate).to.have.been.calledWith( { iofogUuid: uuid }, - { iofogUuid: uuid, versionCommand: 'upgrade' }, + { iofogUuid: uuid, versionCommand: 'upgrade', semver: null }, transaction ) expect(ChangeTrackingService.update).to.have.been.calledWith(uuid, ChangeTrackingService.events.version, transaction) }) + context('when semver is provided', () => { + def('fogVersionData', () => ({ uuid, versionCommand: 'upgrade', semver: '3.2.0' })) + + it('stores semver with version command', async () => { + await $subject + expect(ioFogVersionCommandManager.updateOrCreate).to.have.been.calledWith( + { iofogUuid: uuid }, + { iofogUuid: uuid, versionCommand: 'upgrade', semver: '3.2.0' }, + transaction + ) + }) + }) + context('when upgrade is not allowed', () => { beforeEach(() => { ioFogManager.findOne.resolves({ uuid, isReadyToUpgrade: false, isReadyToRollback: true }) From e0b70bd580ee0832300db69ba21e9eb6772d91a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Fri, 12 Jun 2026 15:28:35 +0300 Subject: [PATCH 75/75] Update greenfield defaults for edgelet container engine socket. Set the v3.8.0 migration and swagger default containerEngineUrl to the edgelet containerd socket, and fix the swagger API title typo. --- docs/swagger.yaml | 19 +++++++++++++++++-- .../mysql/db_migration_mysql_v3.8.0.sql | 3 ++- .../postgres/db_migration_pg_v3.8.0.sql | 3 ++- .../sqlite/db_migration_sqlite_v3.8.0.sql | 3 ++- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index e071aecf..11d7d534 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,7 +1,7 @@ openapi : "3.0.0" info: version: 3.8.0 - title: Cloud Natice Controller for Edge Native Workloads + title: Cloud Native Controller for Edge Native Workloads paths: /status: get: @@ -245,6 +245,17 @@ paths: enum: - upgrade - rollback + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + semver: + type: string + description: Target semver for upgrade or rollback + pattern: ^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:[+]([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ security: - authToken: [] responses: @@ -7380,7 +7391,7 @@ components: type: string containerEngineUrl: type: string - default: unix:///var/run/docker.sock + default: unix:///run/edgelet/contaienrd.sock containerEngine: type: string enum: @@ -9129,6 +9140,10 @@ components: type: string expirationTime: type: string + semver: + type: string + description: Target semver for upgrade or rollback + pattern: ^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:[+]([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ HalInfo: type: object required: diff --git a/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql b/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql index 11e91784..7cfa7ce5 100644 --- a/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql +++ b/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql @@ -89,7 +89,7 @@ CREATE TABLE IF NOT EXISTS Fogs ( catalog_item_message_counts TEXT, last_command_time BIGINT, network_interface VARCHAR(36) DEFAULT 'dynamic', - docker_url VARCHAR(255) DEFAULT 'unix:///var/run/docker.sock', + docker_url VARCHAR(255) DEFAULT 'unix:///run/edgelet/contaienrd.sock', disk_limit FLOAT DEFAULT 50, disk_directory VARCHAR(255) DEFAULT '/var/lib/iofog-agent/', memory_limit FLOAT DEFAULT 4096, @@ -168,6 +168,7 @@ CREATE INDEX idx_fog_provision_keys_iofogUuid ON FogProvisionKeys (iofog_uuid); CREATE TABLE IF NOT EXISTS FogVersionCommands ( id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, version_command VARCHAR(100), + semver VARCHAR(100), iofog_uuid VARCHAR(36), FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE ); diff --git a/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql b/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql index 1114bc14..3ceb8dff 100644 --- a/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql +++ b/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql @@ -87,7 +87,7 @@ CREATE TABLE IF NOT EXISTS "Fogs" ( catalog_item_message_counts TEXT, last_command_time BIGINT, network_interface VARCHAR(36) DEFAULT 'dynamic', - docker_url VARCHAR(255) DEFAULT 'unix:///var/run/docker.sock', + docker_url VARCHAR(255) DEFAULT 'unix:///run/edgelet/contaienrd.sock', disk_limit DOUBLE PRECISION DEFAULT 50, disk_directory VARCHAR(255) DEFAULT '/var/lib/iofog-agent/', memory_limit DOUBLE PRECISION DEFAULT 4096, @@ -166,6 +166,7 @@ CREATE INDEX idx_fog_provision_keys_iofogUuid ON "FogProvisionKeys" (iofog_uuid) CREATE TABLE IF NOT EXISTS "FogVersionCommands" ( id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, version_command VARCHAR(100), + semver VARCHAR(100), iofog_uuid VARCHAR(36), FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE ); diff --git a/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql b/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql index 690a813e..46ef1bd8 100644 --- a/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql +++ b/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql @@ -87,7 +87,7 @@ CREATE TABLE IF NOT EXISTS Fogs ( catalog_item_message_counts TEXT, last_command_time BIGINT, network_interface VARCHAR(36) DEFAULT 'dynamic', - docker_url VARCHAR(255) DEFAULT 'unix:///var/run/docker.sock', + docker_url VARCHAR(255) DEFAULT 'unix:///run/edgelet/contaienrd.sock', disk_limit FLOAT DEFAULT 50, disk_directory VARCHAR(255) DEFAULT '/var/lib/iofog-agent/', memory_limit FLOAT DEFAULT 4096, @@ -166,6 +166,7 @@ CREATE INDEX idx_fog_provision_keys_iofogUuid ON FogProvisionKeys (iofog_uuid); CREATE TABLE IF NOT EXISTS FogVersionCommands ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, version_command VARCHAR(100), + semver VARCHAR(100), iofog_uuid VARCHAR(36), FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE );