Compare commits
141 Commits
Author | SHA1 | Date | |
---|---|---|---|
bc7a2ca5f1 | |||
77d911e47a | |||
b9c9c2d0a9 | |||
d5b91789d1 | |||
eb8350f453 | |||
b987ce27b8 | |||
630e363e53 | |||
a602021952 | |||
80585437a0 | |||
4674a20a2c | |||
820cdfcd48 | |||
6e5dd9b05a | |||
f3d5c21fab | |||
04b278ee28 | |||
7084d76c43 | |||
41d7550e89 | |||
4bf361d3a6 | |||
d70617a90c | |||
62ad1655d5 | |||
caf3a095f2 | |||
89e44b2e5f | |||
a617f51b19 | |||
355e04fd1d | |||
89bd767bea | |||
e567ebbf21 | |||
33311348e2 | |||
d6e914edab | |||
da7b866f23 | |||
7654d780b1 | |||
dbd9b661c6 | |||
e19d0b4deb | |||
f0ebb719f7 | |||
c8e0666bc6 | |||
0d0b106f90 | |||
c9073df7cd | |||
f65200703d | |||
57970b3d10 | |||
b4d9f40c41 | |||
a219725ff6 | |||
4b993fc6b3 | |||
d453da709f | |||
50fac41c95 | |||
affce1fcd1 | |||
df67ebd27a | |||
ef5bfd435a | |||
db07db930c | |||
f6309f600c | |||
7477704905 | |||
db89d86242 | |||
b74ce05845 | |||
79db68a4a2 | |||
5a3ddcf39b | |||
fe6bfc0a83 | |||
36a481ecd1 | |||
f7b2e203ed | |||
27c98c4e32 | |||
79257908d0 | |||
b5ca898eac | |||
53ade28931 | |||
fff4c7642d | |||
dafe6574cc | |||
b70dad4996 | |||
17b0b50fbd | |||
91a0272ab3 | |||
efd22d4087 | |||
c9e32540bf | |||
8344f96983 | |||
3b77089d79 | |||
b6bce76043 | |||
cab57ab303 | |||
804f1f3b12 | |||
f0144fdd5b | |||
81f286cb2f | |||
1f12cb9f94 | |||
26490e8ddd | |||
38d2120c35 | |||
f80b8decbc | |||
28cd6d1b49 | |||
899e5b0a7d | |||
0eff7c7510 | |||
7789348f4e | |||
66a23a515b | |||
7c1082f5a9 | |||
15ea5adec6 | |||
da0dddcceb | |||
b5433e412f | |||
7eb6bf794c | |||
b244518fcb | |||
95d0396abb | |||
a830299cc9 | |||
10fc1d7fba | |||
614ed78928 | |||
ea7f6a6477 | |||
2d746a9d1c | |||
a9fab24961 | |||
5548d5a72d | |||
6b52e05a86 | |||
87e273c30e | |||
0f3f4b8e3f | |||
5f16d8e494 | |||
5148bd1fff | |||
41c54a070c | |||
6956524c6e | |||
7a1d933559 | |||
343acd4997 | |||
337d111cf6 | |||
f49dce92cd | |||
c6abfe69b8 | |||
9d52c62335 | |||
971abd19c9 | |||
084a11d7ed | |||
8f49f0cb4f | |||
320b3ed9eb | |||
7cd6bc0e33 | |||
a218805f27 | |||
6f56304e07 | |||
4eb62200e8 | |||
c105596455 | |||
5aa136b8d9 | |||
240516520a | |||
4e1f9464fe | |||
41f9f93d1c | |||
c502d410b1 | |||
53f96095c7 | |||
d212dfb9f9 | |||
0ec665516d | |||
acc642adf9 | |||
a6521708f7 | |||
206fe445bc | |||
a7ee92cde9 | |||
cdbab26008 | |||
1983c64b77 | |||
a6e3a7f5fe | |||
6dd687012f | |||
55b2872ffc | |||
2e6e7f6ca8 | |||
f453ce3126 | |||
b8dd84b8a6 | |||
338ed5ed75 | |||
482a6a101c | |||
929f250006 |
@ -6,8 +6,8 @@ on:
|
||||
- '**'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
IMAGE: code.foss.global/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
@ -27,7 +27,7 @@ jobs:
|
||||
- name: Install pnpm and npmci
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
@ -54,7 +54,7 @@ jobs:
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Test stable
|
||||
|
@ -6,13 +6,14 @@ on:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_LOGIN_DOCKER_GITEA: ${{ github.server_url }}|${{ gitea.repository_owner }}|${{ secrets.GITEA_TOKEN }}
|
||||
# NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
# NPMCI_LOGIN_DOCKER_GITEA: ${{ github.server_url }}|${{ gitea.repository_owner }}|${{ secrets.GITEA_TOKEN }}
|
||||
NPMCI_LOGIN_DOCKER_DOCKERREGISTRY: ${{ secrets.NPMCI_LOGIN_DOCKER_DOCKERREGISTRY }}
|
||||
NPMCI_SECRET01: ${{ secrets.NPMCI_SECRET01 }}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
@ -27,7 +28,7 @@ jobs:
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
@ -54,7 +55,7 @@ jobs:
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Test stable
|
||||
@ -74,7 +75,7 @@ jobs:
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: registry.gitlab.com/hosttoday/ht-docker-dbase:npmci
|
||||
image: code.foss.global/host.today/ht-docker-dbase:npmci
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@ -82,15 +83,14 @@ jobs:
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
|
||||
- name: Release
|
||||
run: |
|
||||
npmci docker login
|
||||
npmci docker build
|
||||
npmci docker test
|
||||
# npmci docker push gitea.lossless.digital
|
||||
npmci docker push dockerregistry.lossless.digital
|
||||
npmci docker push code.foss.global
|
||||
|
||||
metadata:
|
||||
needs: test
|
||||
|
@ -1,6 +1,6 @@
|
||||
# gitzone dockerfile_service
|
||||
## STAGE 1 // BUILD
|
||||
FROM registry.gitlab.com/hosttoday/ht-docker-node:npmci as node1
|
||||
FROM code.foss.global/host.today/ht-docker-node:npmci as node1
|
||||
COPY ./ /app
|
||||
WORKDIR /app
|
||||
ARG NPMCI_TOKEN_NPM2
|
||||
@ -12,7 +12,7 @@ RUN pnpm run build
|
||||
|
||||
# gitzone dockerfile_service
|
||||
## STAGE 2 // install production
|
||||
FROM registry.gitlab.com/hosttoday/ht-docker-node:npmci as node2
|
||||
FROM code.foss.global/host.today/ht-docker-node:npmci as node2
|
||||
WORKDIR /app
|
||||
COPY --from=node1 /app /app
|
||||
RUN rm -rf .pnpm-store
|
||||
@ -24,7 +24,7 @@ RUN rm -rf node_modules/ && pnpm install --prod
|
||||
|
||||
|
||||
## STAGE 3 // rebuild dependencies for alpine
|
||||
FROM registry.gitlab.com/hosttoday/ht-docker-node:alpinenpmci as node3
|
||||
FROM code.foss.global/host.today/ht-docker-node:alpine_npmci as node3
|
||||
WORKDIR /app
|
||||
COPY --from=node2 /app /app
|
||||
ARG NPMCI_TOKEN_NPM2
|
||||
@ -34,7 +34,7 @@ RUN pnpm config set store-dir .pnpm-store
|
||||
RUN pnpm rebuild -r
|
||||
|
||||
## STAGE 4 // the final production image with all dependencies in place
|
||||
FROM registry.gitlab.com/hosttoday/ht-docker-node:alpine as node4
|
||||
FROM code.foss.global/host.today/ht-docker-node:alpine as node4
|
||||
WORKDIR /app
|
||||
COPY --from=node3 /app /app
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
FROM serve.zone/cloudly:latest
|
447
changelog.md
Normal file
447
changelog.md
Normal file
@ -0,0 +1,447 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-04-25 - 5.0.4 - fix(platformservice/mta)
|
||||
Update getEmailStatus response schema: make details property optional
|
||||
|
||||
- Changed details property from required with fixed message to optional with a flexible message structure in IReq_GetEMailStats response
|
||||
|
||||
## 2025-04-25 - 5.0.3 - fix(mta)
|
||||
update email status response type in MTA platform service
|
||||
|
||||
- Changed the response 'status' field in IRequest_CheckEmailStatus from a literal 'unknown' to a generic string for improved flexibility
|
||||
|
||||
## 2025-04-25 - 5.0.2 - fix(platformservice/mta)
|
||||
Refactor email status response in MTA service
|
||||
|
||||
- Updated IReq_CheckEmailStatus response: replaced union type ('ok' | 'not ok') with fixed status 'unknown' and added a details object with message 'Email not found'.
|
||||
|
||||
## 2025-04-25 - 5.0.1 - fix(mta)
|
||||
Update email stats response interface in mta platform service to include totalEmailsSent, totalEmailsDelivered, totalEmailsBounced, averageDeliveryTimeMs, and lastUpdated timestamp.
|
||||
|
||||
- Modified IReq_GetEMailStats response in ts_interfaces/platformservice/mta.ts from an empty status object to a detailed email statistics structure.
|
||||
|
||||
## 2025-04-25 - 5.0.0 - BREAKING CHANGE(ts_interfaces/platformservice/mta)
|
||||
Rename mta interfaces and upgrade dependency versions
|
||||
|
||||
- Upgraded devDependencies: @git.zone/tsbuild, tsbundle, tsdoc, tstest, tswatch, and @push.rocks/tapbundle to newer versions.
|
||||
- Upgraded dependencies: @design.estate/dees-catalog, dees-domtools, dees-element, @push.rocks/smartdata, smartexpect, smartfile, smartpromise, smartrequest, smartrx, and tsclass (v4.2.0 to v9.0.0).
|
||||
- Added new packageManager field in package.json and introduced pnpm-workspace.yaml for additional workspace configuration.
|
||||
- Refactored mta API interfaces: renamed IRequest_SendEmail to IReq_SendEmail and IRequestRegisterRecipient to IReq_RegisterRecipient; added IReq_CheckEmailStatus and IReq_GetEMailStats.
|
||||
|
||||
## 2025-01-20 - 4.13.0 - feat(service)
|
||||
Add support for service creation, update, and deletion.
|
||||
|
||||
- Implemented TypedHandlers for creating a new service.
|
||||
- Added features to update existing service details.
|
||||
- Enabled deletion of services by their unique ID.
|
||||
|
||||
## 2025-01-20 - 4.12.2 - fix(service)
|
||||
Fix secret bundle and service management bugs
|
||||
|
||||
- Corrected the field name from 'includedImages' to 'imageClaims' in secret bundles.
|
||||
- Implemented 'getFlatKeyValueObject' for secret bundles and modified related API interactions.
|
||||
- Enhanced the Service class with methods for handling secret bundle data by resolving related groups and environments.
|
||||
|
||||
## 2025-01-02 - 4.12.1 - fix(deps)
|
||||
Updated @git.zone/tspublish to version ^1.9.1
|
||||
|
||||
|
||||
## 2025-01-02 - 4.12.0 - feat(cli)
|
||||
Add CLI support and external registries view
|
||||
|
||||
- Adds CLI client functionality
|
||||
- Introduces a new view for External Registries in the dashboard
|
||||
|
||||
## 2024-12-30 - 4.11.0 - feat(external-registry)
|
||||
Introduce external registry management
|
||||
|
||||
- Added ExternalRegistryManager to handle external registry operations.
|
||||
- Implemented ability to create, retrieve, and delete external registries.
|
||||
- Enhanced Cloudly class to include ExternalRegistryManager.
|
||||
|
||||
## 2024-12-29 - 4.10.0 - feat(apiclient)
|
||||
Added support for managing external registries in the API client.
|
||||
|
||||
- Introduced methods to get a registry by ID, get all registries, and create a new registry in the externalRegistry object.
|
||||
- Updated external registry request interfaces to match new API client capabilities.
|
||||
|
||||
## 2024-12-29 - 4.9.0 - feat(apiclient)
|
||||
Add external registry management capabilities to Cloudly API client.
|
||||
|
||||
- Introduce ExternalRegistry class with methods for getting, creating, and updating external registries.
|
||||
- Expand requests module to handle external registry management, including creation and deletion.
|
||||
|
||||
## 2024-12-28 - 4.8.1 - fix(interfaces)
|
||||
Fix image location schema in IImage interface
|
||||
|
||||
- Refactored the 'external' object within IImage data to a 'location' object.
|
||||
- Added 'internal' boolean to 'location' to specify internal/external status.
|
||||
|
||||
## 2024-12-28 - 4.8.0 - feat(manager.registry)
|
||||
Add external registry management
|
||||
|
||||
- Introduced ExternalRegistry class for handling external registry configurations.
|
||||
- Updated IExternalRegistry interface to include registry details.
|
||||
- Enhanced IImage interface to support linking with external registries.
|
||||
|
||||
## 2024-12-28 - 4.7.1 - fix(secretmanagement)
|
||||
Refactor secret bundle actions and improve authorization handling
|
||||
|
||||
- Refactored secret bundle handling by renaming methods and reorganizing static and instance methods in SecretBundle class.
|
||||
- Added getSecretBundleByAuthorization method to SecretBundle.
|
||||
- Improved getFlatKeyValueObjectForEnvironment to accurately retrieve key-value pairs for specified environments.
|
||||
- Removed deprecated IEnvBundle interface and related request handler for better clarity and code usage.
|
||||
- Updated request interfaces related to secret bundles for consistent method naming and arguments.
|
||||
|
||||
## 2024-12-22 - 4.7.0 - feat(apiclient)
|
||||
Add method to flatten secret bundles into key-value objects.
|
||||
|
||||
- SecretBundle: Implemented toFlatKeyValueObject method to flatten secret groups into key-value pairs.
|
||||
- Removed stale SecretManager class from apiclient.
|
||||
|
||||
## 2024-12-22 - 4.6.0 - feat(cloudlyapiclient)
|
||||
Extend CloudlyApiClient with cluster, secretbundle, and secretgroup methods
|
||||
|
||||
- Added methods to CloudlyApiClient for managing clusters: getClusterById, getClusters, createCluster.
|
||||
- Added methods to CloudlyApiClient for managing secret bundles: getSecretBundleById, getSecretBundles, createSecretBundle.
|
||||
- Added methods to CloudlyApiClient for managing secret groups: getSecretGroupById, getSecretGroups, createSecretGroup.
|
||||
|
||||
## 2024-12-22 - 4.5.5 - fix(apiclient)
|
||||
Fixed image creation method in cloudlyApiClient
|
||||
|
||||
- Corrected method call from 'images.createImage' to 'image.createImage' to ensure proper image creation.
|
||||
- Updated cluster retrieval methods and ensured proper API routes are being called.
|
||||
|
||||
## 2024-12-21 - 4.5.4 - fix(ts_web)
|
||||
Fix action type and data fields in appstate for CRUD operations
|
||||
|
||||
- Correct request method in createSecretGroupAction to accurately reflect the purpose.
|
||||
- Align the deleteSecretGroupAction and deleteSecretBundleAction request types with proper interfaces.
|
||||
- Ensure data payload matches backend requirements for secret group and secret bundle operations.
|
||||
|
||||
## 2024-12-21 - 4.5.3 - fix(secret-management)
|
||||
Refactor secret management to use distinct secret bundle and group APIs. Introduce API client classes for secret bundles and groups.
|
||||
|
||||
- Updated secret management logic to separate secret bundle and group APIs.
|
||||
- Implemented new API client classes for managing secret bundles and groups.
|
||||
- Fixed incorrect method usages for secret-related actions.
|
||||
|
||||
## 2024-12-20 - 4.5.2 - fix(apiclient)
|
||||
Implemented IService interface in Service class and improved secret bundle documentation.
|
||||
|
||||
- Implemented plugins.servezoneInterfaces.data.IService interface in the Service class within ts_apiclient.
|
||||
- Updated documentation comments for the type property in the ISecretBundle interface.
|
||||
|
||||
## 2024-12-17 - 4.5.1 - fix(core)
|
||||
Updated dependencies in package.json to latest versions.
|
||||
|
||||
- Bumped @git.zone/tswatch to version ^2.0.37
|
||||
- Bumped @types/node to version ^22.10.2
|
||||
- Bumped @design.estate/dees-catalog to version ^1.3.2
|
||||
- Bumped @push.rocks/smartfile to version ^11.0.23
|
||||
- Bumped @tsclass/tsclass to version ^4.2.0
|
||||
|
||||
## 2024-12-14 - 4.5.0 - feat(services)
|
||||
Add service management functionalities
|
||||
|
||||
- Integrated service-related API client methods including getServices, getServiceById, and createService.
|
||||
- Updated the deployment data structure in the service manager.
|
||||
- Enhanced service interface to incorporate additional fields for comprehensive data handling.
|
||||
- Ensured secure token generation for Cloudly authentication processes.
|
||||
|
||||
## 2024-11-18 - 4.4.0 - feat(api-client)
|
||||
Add static method getImageById for Image class in api-client
|
||||
|
||||
- Introduced a static method getImageById in the Image class.
|
||||
- Updated CloudlyApiClient to include the getImageById method in the images interface.
|
||||
|
||||
## 2024-11-18 - 4.3.21 - fix(interfaces)
|
||||
Remove deprecated deployment directive and update related interfaces
|
||||
|
||||
- Removed IDeploymentDirective from data and requests.
|
||||
- Updated IDeployment to remove references to directives.
|
||||
- Changed IRequest_Any_Cloudly_GetClusterConfig to return services instead of deployment directives.
|
||||
- Removed deploymentDirectiveIds from IService data structure.
|
||||
|
||||
## 2024-11-18 - 4.3.20 - fix(apiclient)
|
||||
Ensure mandatory parameter in CloudlyApiClient constructor
|
||||
|
||||
- Made the 'optionsArg' parameter mandatory in the constructor of CloudlyApiClient class.
|
||||
|
||||
## 2024-11-18 - 4.3.19 - fix(docker)
|
||||
Fix improper Docker push command preventing push to the correct registry.
|
||||
|
||||
- Corrected the docker push command in the '.gitea/workflows/docker_tags.yaml' file to push images to the 'code.foss.global' registry.
|
||||
|
||||
## 2024-11-17 - 4.3.18 - fix(docker_tags)
|
||||
Updated Docker configuration to include NPM tokens.
|
||||
|
||||
- Restored NPMCI_TOKEN_NPM and NPMCI_TOKEN_NPM2 environment variables in docker_tags.yaml for authentication purposes.
|
||||
|
||||
## 2024-11-17 - 4.3.17 - fix(Dockerfile)
|
||||
Corrected docker base image tag in Dockerfile for alpine compatibility.
|
||||
|
||||
- Updated Dockerfile to use the correct base image tag for Alpine.
|
||||
- Resolved any potential build issues related to incorrect image tag usage.
|
||||
|
||||
## 2024-11-17 - 4.3.16 - fix(infrastructure)
|
||||
Correct Docker image path in Dockerfile for improved build consistency.
|
||||
|
||||
- Dockerfile: Updated base image paths from 'code.foss.global/hosttoday/ht-docker-node' to 'code.foss.global/host.today/ht-docker-node'.
|
||||
|
||||
## 2024-11-17 - 4.3.15 - fix(project setup)
|
||||
fixed incorrect configuration in npmextra.json
|
||||
|
||||
- Removed unnecessary 'dockerBuildargEnvMap' entry for NPMCI_TOKEN_NPM2.
|
||||
- Corrected the githost and gitscope in gitzone module configuration.
|
||||
- Updated the license field to reflect the correct license.
|
||||
|
||||
## 2024-11-16 - 4.3.14 - fix(docker tags)
|
||||
Comment out unused secret variables in docker_tags.yaml
|
||||
|
||||
- Modified docker_tags.yaml to comment out unused secret variables related to NPM and GitHub tokens.
|
||||
|
||||
## 2024-11-16 - 4.3.13 - fix(package)
|
||||
Updated package dependencies
|
||||
|
||||
- Updated @design.estate/dees-catalog version to 1.3.1
|
||||
|
||||
## 2024-11-06 - 4.3.12 - fix(workflow)
|
||||
Fix Docker image path in GitHub action workflow
|
||||
|
||||
- Corrected the path of the Docker image used in the GitHub action workflow from 'code.foss.global/hosttoday/ht-docker-dbase:npmci' to 'code.foss.global/host.today/ht-docker-dbase:npmci'.
|
||||
|
||||
## 2024-11-06 - 4.3.11 - fix(overall)
|
||||
Refactor and improve code consistency across all modules
|
||||
|
||||
- Updated cloud configuration management for better reliability.
|
||||
- Enhanced security measures in the authentication and authorization processes.
|
||||
- Streamlined deployment logic in cluster management.
|
||||
- Refactored code to improve maintainability and readability.
|
||||
|
||||
## 2024-11-06 - 4.3.10 - fix(dependencies)
|
||||
Updated dependencies and fixed Docker Alpine image retrieval issue in tests
|
||||
|
||||
- Updated @push.rocks/tapbundle to version ^5.5.0 in devDependencies.
|
||||
- Updated @push.rocks/smartrequest to version ^2.0.23 in dependencies.
|
||||
- Ensured the Docker Alpine image is retrieved as a local tarball in cloudlyfactory.ts test helper.
|
||||
|
||||
## 2024-11-06 - 4.3.9 - fix(test and dependencies)
|
||||
Corrected cloudlyUrl in test.apiclient and updated tapbundle dependency.
|
||||
|
||||
- Fixed cloudlyUrl assignment in the test.apiclient to use the correct helper method.
|
||||
- Updated tapbundle dependency version from ^5.4.3 to ^5.4.4.
|
||||
|
||||
## 2024-11-06 - 4.3.8 - fix(api client)
|
||||
Fixed localhost URL issue in test.client.ts
|
||||
|
||||
- Changed the cloudlyUrl in test.client.ts from 'localhost' to '127.0.0.1' to ensure consistency in network requests.
|
||||
|
||||
## 2024-11-06 - 4.3.7 - fix(tests)
|
||||
Refactored test setup for consistency and isolated config initialization.
|
||||
|
||||
- test/helpers/cloudlyfactory.ts: Test configuration setup was refactored to ensure consistent initialization of cloudly configuration across tests.
|
||||
- test/test.apiclient.ts: Updated cloudlyApiClient test setup to use testCloudlyConfig for dynamic port allocation.
|
||||
|
||||
## 2024-11-06 - 4.3.6 - fix(test)
|
||||
Enhance test helpers with dynamic Hetzner token retrieval.
|
||||
|
||||
- Updated test/helpers/cloudlyfactory.ts to retrieve Hetzner token from environment variables.
|
||||
- Expanded docker_tags workflow to securely handle and pass new environment secrets.
|
||||
|
||||
## 2024-11-06 - 4.3.5 - fix(helpers)
|
||||
Add missing sslMode configuration to Cloudly config.
|
||||
|
||||
- Added the sslMode key with a value of 'none' to the Cloudly configuration object in test/helpers/cloudlyfactory.ts.
|
||||
|
||||
## 2024-11-06 - 4.3.4 - fix(testing)
|
||||
Fixed Cloudly testing setup and dependencies
|
||||
|
||||
- Updated devDependency @push.rocks/tapbundle version from ^5.3.0 to ^5.4.3 in package.json.
|
||||
- Updated devDependency @push.rocks/npmextra version from ^5.1.1 to ^5.1.2 in package.json.
|
||||
- Improved the Cloudly test suite setup to ensure proper initialization of MongoDB and S3 services.
|
||||
- Ensured asynchronous stopping and cleanup of MongoDB and S3 services post-test execution.
|
||||
|
||||
## 2024-11-05 - 4.3.3 - fix(core)
|
||||
Fix configuration initialization by accepting a config argument
|
||||
|
||||
- Configuration initialization now accepts an optional config argument
|
||||
- Updated test cloudly factory to use default public URL and port
|
||||
- Updated dependencies versions
|
||||
|
||||
## 2024-11-05 - 4.3.2 - fix(npmextra)
|
||||
Updated npm registry URL in npmextra.json
|
||||
|
||||
|
||||
## 2024-11-05 - 4.3.1 - fix(package)
|
||||
Update dependency version for @git.zone/tspublish
|
||||
|
||||
- Bump @git.zone/tspublish from version ^1.7.6 to ^1.7.7 in package.json
|
||||
|
||||
## 2024-11-05 - 4.3.0 - feat(dependencies)
|
||||
Upgrade dependencies and include publish orders
|
||||
|
||||
- Upgraded @git.zone/tsbuild to version ^2.2.0
|
||||
- Upgraded @git.zone/tspublish to version ^1.7.6
|
||||
- Upgraded @types/node to version ^22.8.7
|
||||
- Added publish order to ts_apiclient/tspublish.json and ts_interfaces/tspublish.json
|
||||
|
||||
## 2024-11-04 - 4.2.1 - fix(config)
|
||||
Fix Docker image URL in Gitea workflow.
|
||||
|
||||
- Corrected the IMAGE URL from 'hosttoday' to 'host.today'.
|
||||
|
||||
## 2024-11-04 - 4.2.0 - feat(cloudron)
|
||||
Add Dockerfile for Cloudron deployment
|
||||
|
||||
- Introduced a new Dockerfile for Cloudron deployment.
|
||||
- The Dockerfile uses the latest version of cloudly as a base image.
|
||||
|
||||
## 2024-10-28 - 4.1.3 - fix(dependency)
|
||||
Updated dependency @git.zone/tspublish to version ^1.6.0
|
||||
|
||||
- Bumped @git.zone/tspublish version from ^1.5.5 to ^1.6.0 in package.json
|
||||
|
||||
## 2024-10-28 - 4.1.2 - fix(core)
|
||||
Corrected description and devDependencies
|
||||
|
||||
- Updated package.json description to accurately reflect features.
|
||||
- Added `@git.zone/tsdoc` to devDependencies.
|
||||
- Corrected version discrepancy in `@types/node` devDependency.
|
||||
- Standardized description across multiple files including npmextra.json.
|
||||
|
||||
## 2024-10-28 - 4.1.1 - fix(core)
|
||||
Fixed syntax issues in commitinfo data and package.json file.
|
||||
|
||||
- Added a missing newline at the end of the package.json file.
|
||||
- Corrected a trailing comma and added proper syntax in the commitinfo data.
|
||||
|
||||
## 2024-10-28 - 4.1.0 - feat(core)
|
||||
Enhance core functionality for cloud management and orchestration
|
||||
|
||||
- Improved initialization and management of cloud environments with Docker Swarmkit.
|
||||
- Added capability to manage DNS records via Cloudflare.
|
||||
- Introduced integration support for DigitalOcean resources.
|
||||
|
||||
## 2024-10-28 - 4.0.1 - fix(package_manager)
|
||||
Update @git.zone/tspublish dependency version
|
||||
|
||||
- Bump @git.zone/tspublish version from 1.5.4 to 1.5.5.
|
||||
|
||||
## 2024-10-28 - 4.0.0 - BREAKING CHANGE(core)
|
||||
Significant overhaul with potential breaking changes, update to version 3.0.0
|
||||
|
||||
- Updated project version from 1.2.5 to 3.0.0 in package.json
|
||||
|
||||
## 2024-10-28 - 1.2.5 - fix(build)
|
||||
Updated devDependencies for tspublish and removed buildDocs script
|
||||
|
||||
- devDependencies updated: @git.zone/tspublish to version ^1.5.4
|
||||
- Removed: buildDocs script from the scripts section
|
||||
|
||||
## 2024-10-27 - 1.2.4 - fix(ci)
|
||||
Fix Docker images and npm registry URL in CI workflows
|
||||
|
||||
- Updated Docker image registry URL from 'registry.gitlab.com' to 'code.foss.global'.
|
||||
- Fixed npmci package installation path from '@shipzone/npmci' to '@ship.zone/npmci'.
|
||||
|
||||
## 2024-10-23 - 1.2.3 - fix(cli)
|
||||
Set up CLI client definition and registry configuration
|
||||
|
||||
- Added a console log message for CLI identification.
|
||||
- Defined package name for CLI client.
|
||||
- Configured npm registries for package distribution.
|
||||
|
||||
## 2024-10-23 - 1.2.2 - fix(docs)
|
||||
Updated documentation with clearer usage instructions and examples.
|
||||
|
||||
- Refined setup guides in README.
|
||||
- Added more detailed examples in code snippets.
|
||||
- Clarified cloud service integration instructions.
|
||||
|
||||
## 2024-10-23 - 1.2.1 - fix(core)
|
||||
Fixed startup issue for the Cloudly instance
|
||||
|
||||
|
||||
## 2024-10-21 - 1.2.0 - feat(cli)
|
||||
Add tspublish.json for CLI client and interfaces
|
||||
|
||||
- Added registration details for publishing CLI client (@serve.zone/cli)
|
||||
- Specified npm and custom registries in the tspublish.json for better publish control
|
||||
- Updated dependency version for @git.zone/tspublish in package.json
|
||||
|
||||
## 2024-10-21 - 1.1.9 - fix(build)
|
||||
Update Node types and other dependencies, add tspublish.json for api client
|
||||
|
||||
- Updated Node types devDependency to version ^22.7.7 from ^22.7.5.
|
||||
- Updated smartbucket dependency to version ^3.0.23 from ^3.0.22.
|
||||
- Added tspublish configuration file to the api client.
|
||||
- Fixed the npm publish script in package.json.
|
||||
|
||||
## 2024-10-16 - 1.1.8 - fix(big fix upgrade)
|
||||
fix: update dependency versions and address type errors
|
||||
|
||||
- Updated all listed dependencies in the package.json to their specified ranges.
|
||||
- Fixed type mismatches and added missing imports in various TypeScript files.
|
||||
- Refined existing tests and added a new helper to manage Docker image streams.
|
||||
|
||||
## 2024-08-25 - 1.1.7 - fix(deps)
|
||||
Update dependencies to latest versions
|
||||
|
||||
- Updated @git.zone/tsbuild from ^2.1.80 to ^2.1.84
|
||||
- Updated @push.rocks/tapbundle from ^5.0.23 to ^5.0.24
|
||||
- Updated @types/node from ^20.14.6 to ^22.5.0
|
||||
- Updated @apiclient.xyz/docker from ^1.2.2 to ^1.2.3
|
||||
- Updated @design.estate/dees-catalog from ^1.0.289 to ^1.1.6
|
||||
- Updated @design.estate/dees-element from ^2.0.34 to ^2.0.36
|
||||
- Updated @git.zone/tsrun from ^1.2.37 to ^1.2.49
|
||||
- Updated @push.rocks/smartbucket from ^3.0.20 to ^3.0.22
|
||||
- Updated @push.rocks/smartpromise from ^4.0.3 to ^4.0.4
|
||||
- Updated @serve.zone/interfaces from ^1.0.74 to ^1.0.78
|
||||
- Updated @tsclass/tsclass from ^4.0.60 to ^4.1.2
|
||||
|
||||
## 2024-06-20 - 1.1.6 - Updates
|
||||
Routine updates and fixes.
|
||||
|
||||
- (fix) core: update
|
||||
|
||||
## 2024-06-13 - 1.1.4 - Service Management Preparation
|
||||
Incorporated updates and service management preparations.
|
||||
|
||||
- (fix) core: update
|
||||
- (feat) prepare service management
|
||||
|
||||
## 2024-06-05 - 1.1.3 - CI Integration Improvement
|
||||
Structural improvements and better CI integration preparation.
|
||||
|
||||
- (fix) structure: improve structure, prepare better CI integration
|
||||
|
||||
## 2024-06-02 - 1.1.2 - Image Manager Update
|
||||
Prepared proper storage and retrieval of container images.
|
||||
|
||||
- (fix) imagemanager: prepare proper storage and retrieval of container images
|
||||
|
||||
## 2024-06-01 - 1.1.0 - Image Registry Work
|
||||
Initiated work on image registry.
|
||||
|
||||
- (fix) image registry: start work on image registry
|
||||
|
||||
## 2024-05-30 - 1.0.216 - Enhanced Smartguards
|
||||
Enhanced smartguards to verify action authorization.
|
||||
|
||||
- (feat) guards: use better smartguards to verify action authorization
|
||||
|
||||
## 2024-05-28 - 1.0.215 - Unified Package Update
|
||||
Updated package unification for cloudly + API + CLI.
|
||||
|
||||
- (fix) switch to unified package for cloudly + API + CLI: update
|
||||
|
||||
## 2024-05-05 - 1.0.214 - Core Updates
|
||||
Routine core updates.
|
||||
|
||||
- (fix) core: update
|
||||
|
||||
## 2024-04-20 - 1.0.213 - Core Update
|
||||
Routine core updates.
|
||||
|
||||
- (fix) core: update
|
19
license
Normal file
19
license
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2014 Task Venture Capital GmbH (hello@task.vc)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -2,40 +2,48 @@
|
||||
"npmci": {
|
||||
"npmGlobalTools": [],
|
||||
"npmAccessLevel": "public",
|
||||
"npmRegistryUrl": "verdaccio.lossless.one",
|
||||
"npmRegistryUrl": "verdaccio.lossless.digital",
|
||||
"dockerRegistryRepoMap": {
|
||||
"registry.gitlab.com": "losslessone/services/servezone/cloudly"
|
||||
"code.foss.global": "serve.zone/cloudly"
|
||||
},
|
||||
"dockerBuildargEnvMap": {
|
||||
"NPMCI_TOKEN_NPM2": "NPMCI_TOKEN_NPM2"
|
||||
}
|
||||
"dockerBuildargEnvMap": {}
|
||||
},
|
||||
"gitzone": {
|
||||
"projectType": "service",
|
||||
"module": {
|
||||
"githost": "gitlab.com",
|
||||
"gitscope": "servezone/private",
|
||||
"githost": "code.foss.global",
|
||||
"gitscope": "serve.zone",
|
||||
"gitrepo": "cloudly",
|
||||
"description": "A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.",
|
||||
"description": "A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.",
|
||||
"npmPackagename": "@serve.zone/cloudly",
|
||||
"license": "UNLICENSED",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"cloud management",
|
||||
"multi-cloud management",
|
||||
"Docker Swarmkit",
|
||||
"multi-cloud",
|
||||
"container orchestration",
|
||||
"cloud services",
|
||||
"API integration",
|
||||
"web interface",
|
||||
"CLI",
|
||||
"CI/CD integration",
|
||||
"cloud providers",
|
||||
"DigitalOcean",
|
||||
"Hetzner Cloud",
|
||||
"Cloudflare",
|
||||
"container orchestration",
|
||||
"TypeScript",
|
||||
"node.js",
|
||||
"Node.js",
|
||||
"infrastructure automation",
|
||||
"Cloudron",
|
||||
"configuration management",
|
||||
"SSL management",
|
||||
"APIs",
|
||||
"devOps",
|
||||
"cloud integration"
|
||||
"secret management",
|
||||
"configuration management",
|
||||
"task scheduling",
|
||||
"logging",
|
||||
"SSL management",
|
||||
"system logging",
|
||||
"cloud API client",
|
||||
"frontend",
|
||||
"backend",
|
||||
"security"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
103
package.json
103
package.json
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@serve.zone/cloudly",
|
||||
"version": "1.0.216",
|
||||
"version": "5.0.4",
|
||||
"private": false,
|
||||
"description": "A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.",
|
||||
"description": "A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
@ -18,55 +18,63 @@
|
||||
"start": "node cli.js",
|
||||
"startTs": "node cli.ts.js",
|
||||
"watch": "tswatch website",
|
||||
"localPublish": "gitzone commit"
|
||||
"publish": "tspublish",
|
||||
"docs": "tsdoc aidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.1.80",
|
||||
"@git.zone/tsbundle": "^2.0.15",
|
||||
"@git.zone/tstest": "^1.0.90",
|
||||
"@git.zone/tswatch": "^2.0.23",
|
||||
"@push.rocks/tapbundle": "^5.0.23",
|
||||
"@types/node": "^20.12.12"
|
||||
"@git.zone/tsbuild": "^2.3.2",
|
||||
"@git.zone/tsbundle": "^2.2.5",
|
||||
"@git.zone/tsdoc": "^1.4.4",
|
||||
"@git.zone/tspublish": "^1.9.1",
|
||||
"@git.zone/tstest": "^1.0.96",
|
||||
"@git.zone/tswatch": "^2.1.0",
|
||||
"@push.rocks/tapbundle": "^5.6.3",
|
||||
"@types/node": "^22.15.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@api.global/typedrequest": "3.0.25",
|
||||
"@api.global/typedserver": "^3.0.50",
|
||||
"@api.global/typedrequest": "3.1.10",
|
||||
"@api.global/typedrequest-interfaces": "^3.0.19",
|
||||
"@api.global/typedserver": "^3.0.74",
|
||||
"@api.global/typedsocket": "^3.0.1",
|
||||
"@apiclient.xyz/cloudflare": "^6.0.1",
|
||||
"@apiclient.xyz/digitalocean": "^1.0.5",
|
||||
"@apiclient.xyz/hetznercloud": "^1.0.18",
|
||||
"@apiclient.xyz/docker": "^1.3.0",
|
||||
"@apiclient.xyz/hetznercloud": "^1.2.0",
|
||||
"@apiclient.xyz/slack": "^3.0.9",
|
||||
"@design.estate/dees-catalog": "^1.0.289",
|
||||
"@design.estate/dees-domtools": "^2.0.57",
|
||||
"@design.estate/dees-element": "^2.0.34",
|
||||
"@git.zone/tsrun": "^1.2.37",
|
||||
"@design.estate/dees-catalog": "^1.8.0",
|
||||
"@design.estate/dees-domtools": "^2.3.2",
|
||||
"@design.estate/dees-element": "^2.0.42",
|
||||
"@git.zone/tsrun": "^1.3.3",
|
||||
"@push.rocks/early": "^4.0.3",
|
||||
"@push.rocks/npmextra": "^5.0.13",
|
||||
"@push.rocks/npmextra": "^5.1.2",
|
||||
"@push.rocks/projectinfo": "^5.0.1",
|
||||
"@push.rocks/qenv": "^6.0.5",
|
||||
"@push.rocks/smartacme": "^4.0.8",
|
||||
"@push.rocks/smartbucket": "^3.0.9",
|
||||
"@push.rocks/qenv": "^6.1.0",
|
||||
"@push.rocks/smartacme": "^5.0.0",
|
||||
"@push.rocks/smartbucket": "^3.3.7",
|
||||
"@push.rocks/smartcli": "^4.0.11",
|
||||
"@push.rocks/smartdata": "^5.2.1",
|
||||
"@push.rocks/smartclickhouse": "^2.0.17",
|
||||
"@push.rocks/smartdata": "^5.15.1",
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
"@push.rocks/smartexit": "^1.0.23",
|
||||
"@push.rocks/smartfile": "^11.0.15",
|
||||
"@push.rocks/smartguard": "^2.0.1",
|
||||
"@push.rocks/smartexpect": "^1.6.1",
|
||||
"@push.rocks/smartfile": "^11.2.0",
|
||||
"@push.rocks/smartguard": "^3.1.0",
|
||||
"@push.rocks/smartjson": "^5.0.19",
|
||||
"@push.rocks/smartjwt": "^2.0.4",
|
||||
"@push.rocks/smartlog": "^3.0.6",
|
||||
"@push.rocks/smartlog-destination-clickhouse": "^1.0.11",
|
||||
"@push.rocks/smartjwt": "^2.2.1",
|
||||
"@push.rocks/smartlog": "^3.0.7",
|
||||
"@push.rocks/smartlog-destination-clickhouse": "^1.0.13",
|
||||
"@push.rocks/smartlog-interfaces": "^3.0.2",
|
||||
"@push.rocks/smartpath": "^5.0.18",
|
||||
"@push.rocks/smartpromise": "^4.0.3",
|
||||
"@push.rocks/smartrequest": "^2.0.22",
|
||||
"@push.rocks/smartrx": "^3.0.7",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartrequest": "^2.1.0",
|
||||
"@push.rocks/smartrx": "^3.0.10",
|
||||
"@push.rocks/smartssh": "^2.0.1",
|
||||
"@push.rocks/smartstate": "^2.0.19",
|
||||
"@push.rocks/smartstream": "^3.2.5",
|
||||
"@push.rocks/smartstring": "^4.0.15",
|
||||
"@push.rocks/smartunique": "^3.0.9",
|
||||
"@push.rocks/taskbuffer": "^3.0.2",
|
||||
"@push.rocks/webjwt": "^1.0.9",
|
||||
"@serve.zone/interfaces": "^1.0.56",
|
||||
"@tsclass/tsclass": "^4.0.54"
|
||||
"@tsclass/tsclass": "^9.0.0"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
@ -92,21 +100,32 @@
|
||||
},
|
||||
"homepage": "https://gitlab.com/servezone/private/cloudly#readme",
|
||||
"keywords": [
|
||||
"cloud management",
|
||||
"multi-cloud management",
|
||||
"Docker Swarmkit",
|
||||
"multi-cloud",
|
||||
"container orchestration",
|
||||
"cloud services",
|
||||
"API integration",
|
||||
"web interface",
|
||||
"CLI",
|
||||
"CI/CD integration",
|
||||
"cloud providers",
|
||||
"DigitalOcean",
|
||||
"Hetzner Cloud",
|
||||
"Cloudflare",
|
||||
"container orchestration",
|
||||
"TypeScript",
|
||||
"node.js",
|
||||
"Node.js",
|
||||
"infrastructure automation",
|
||||
"Cloudron",
|
||||
"configuration management",
|
||||
"SSL management",
|
||||
"APIs",
|
||||
"devOps",
|
||||
"cloud integration"
|
||||
]
|
||||
"secret management",
|
||||
"configuration management",
|
||||
"task scheduling",
|
||||
"logging",
|
||||
"SSL management",
|
||||
"system logging",
|
||||
"cloud API client",
|
||||
"frontend",
|
||||
"backend",
|
||||
"security"
|
||||
],
|
||||
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
|
||||
}
|
||||
|
9598
pnpm-lock.yaml
generated
9598
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
4
pnpm-workspace.yaml
Normal file
4
pnpm-workspace.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
- mongodb-memory-server
|
||||
- puppeteer
|
@ -0,0 +1,11 @@
|
||||
- This repository contains 4 projects around serve.zone
|
||||
- the cloudly backend under ts/*
|
||||
- the cloudly frontend under ts_web/*
|
||||
- the api client under ts_apiclient
|
||||
- the cli client under ts_cliclient
|
||||
|
||||
- the easiest method to spawn up a cloudly instance is to use the docker image:
|
||||
`code.foss.global/serve.zone/cloudly:latest`
|
||||
|
||||
- Note: the exports are defined in the package.json.
|
||||
- For now, cloud wise only the setup with cloudron and hetzner cloud is supported.
|
521
readme.md
521
readme.md
@ -1,390 +1,335 @@
|
||||
# @serve.zone/cloudly
|
||||
A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.
|
||||
|
||||
A multi-cloud management tool utilizing Docker Swarmkit for orchestrating containerized apps across various cloud providers, with web, CLI, and API interfaces for configuration and integration management.
|
||||
|
||||
## Install
|
||||
|
||||
To install `@serve.zone/cloudly`, run the following command in your terminal:
|
||||
|
||||
```bash
|
||||
npm install @serve.zone/cloudly --save
|
||||
```
|
||||
|
||||
This will install the package and add it to your project's `package.json` dependencies.
|
||||
|
||||
## Usage
|
||||
`@serve.zone/cloudly` is designed to help you manage and configure cloud environments. This package provides a comprehensive TypeScript and ESM-based interface for interacting with various cloud services, including Docker Swarmkit cluster management, and integration with cloud providers such as DigitalOcean, Hetzner Cloud, and Cloudflare.
|
||||
|
||||
### Getting Started
|
||||
Before diving into the specifics, ensure your environment is properly set up. This includes having Node.js installed (preferably the latest LTS version), and if you are working in a TypeScript project, ensure TypeScript is configured.
|
||||
`@serve.zone/cloudly` is designed to provide a unified interface for managing multi-cloud environments, encapsulating complex cloud interactions with Docker Swarmkit into simpler, programmable entities. This document will guide you through various use-cases and implementation examples to give you a comprehensive understanding of the module's capabilities.
|
||||
|
||||
#### Initializing Cloudly
|
||||
First, import `Cloudly` class from the package and initialize it as shown below:
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, ensure your environment is set up correctly:
|
||||
- You have Node.js installed (preferably the latest LTS version).
|
||||
- Your environment is configured to use TypeScript if you're working in a TypeScript project.
|
||||
|
||||
### Basic Setup
|
||||
|
||||
#### Creating a Cloudly Instance
|
||||
|
||||
The foundation of working with `@serve.zone/cloudly` involves creating an instance of the `Cloudly` class. This instance serves as the gateway to managing cloud resources and orchestrates interactions within the platform. Here’s how to get started:
|
||||
|
||||
```typescript
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
import { Cloudly, ICloudlyConfig } from '@serve.zone/cloudly';
|
||||
|
||||
const myCloudlyInstance = new Cloudly();
|
||||
```
|
||||
|
||||
The `Cloudly` class is the entry point to using the library features. It prepares the environment for configuring the cloud services.
|
||||
|
||||
#### Configuration
|
||||
Configuration plays a pivotal role in how `@serve.zone/cloudly` operates. The library expects certain configurations to be provided, which can include credentials for cloud services, database connections, etc.
|
||||
|
||||
For example, to configure a connection to MongoDB, specify your MongoDB details as shown:
|
||||
|
||||
```typescript
|
||||
const myCloudlyConfig = {
|
||||
const myCloudlyConfig: ICloudlyConfig = {
|
||||
cfToken: 'your_cloudflare_api_token',
|
||||
hetznerToken: 'your_hetzner_api_token',
|
||||
environment: 'development',
|
||||
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||
publicUrl: 'example.com',
|
||||
publicPort: '8443',
|
||||
mongoDescriptor: {
|
||||
mongoDbUrl: 'mongodb+srv://<username>:<password>@<cluster>.mongodb.net/myFirstDatabase',
|
||||
mongoDbName: 'myDatabase',
|
||||
mongoDbUser: 'myUser',
|
||||
mongoDbPass: 'myPassword',
|
||||
},
|
||||
// Additional configuration values...
|
||||
};
|
||||
|
||||
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
|
||||
```
|
||||
|
||||
#### Managing Docker Swarmkit Cluster
|
||||
Cloudly allows managing Docker Swarmkit clusters through an abstracted interface, simplifying operations such as deployment and scaling. Below are examples to demonstrate these capabilities.
|
||||
The configuration object `ICloudlyConfig` provides essential information needed for initializing external services, such as Cloudflare, Hetzner, and a MongoDB server. Adjust the parameters to match your actual service credentials and specifications.
|
||||
|
||||
### Example: Start a Cloudly Instance and Add a Cluster
|
||||
### Core Features and Use Cases
|
||||
|
||||
#### Orchestrating Docker Swarmkit Clusters
|
||||
|
||||
Docker Swarmkit cluster management is a primary feature of `@serve.zone/cloudly`. Through its abstracted, programmable interface, you can operate clusters effortlessly. Here’s an example of how to create a cluster using `Cloudly`:
|
||||
|
||||
```typescript
|
||||
import { Cloudly, ClusterManager } from '@serve.zone/cloudly';
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
|
||||
async function main() {
|
||||
interface ICluster {
|
||||
name: string;
|
||||
id: string;
|
||||
cloudlyUrl: string;
|
||||
servers: string[];
|
||||
sshKeys: string[];
|
||||
}
|
||||
|
||||
async function manageClusters() {
|
||||
const myCloudlyConfig = {
|
||||
cfToken: 'your_cloudflare_api_token',
|
||||
environment: 'development',
|
||||
mongoDescriptor: {
|
||||
mongoDbUrl: 'mongodb+srv://<username>:<password>@<cluster>.mongodb.net/myFirstDatabase',
|
||||
mongoDbName: 'myDatabase',
|
||||
mongoDbUser: 'myUser',
|
||||
mongoDbPass: 'myPassword',
|
||||
},
|
||||
cfToken: 'your_cloudflare_api_token',
|
||||
environment: 'development',
|
||||
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||
publicUrl: 'example.com',
|
||||
publicPort: 8443,
|
||||
hetznerToken: 'your_hetzner_api_token'
|
||||
hetznerToken: 'your_hetzner_api_token',
|
||||
};
|
||||
|
||||
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
|
||||
await myCloudlyInstance.start();
|
||||
|
||||
const clusterManager = myCloudlyInstance.clusterManager;
|
||||
const newCluster = await clusterManager.storeCluster({
|
||||
const newCluster: ICluster = {
|
||||
name: 'example_cluster',
|
||||
id: 'example_cluster_id',
|
||||
data: {
|
||||
name: 'example_cluster',
|
||||
jumpCode: 'random_jump_code',
|
||||
jumpCodeUsedAt: null,
|
||||
secretKey: 'example_secret_key',
|
||||
acmeInfo: null,
|
||||
cloudlyUrl: 'https://example.com:8443',
|
||||
servers: [],
|
||||
sshKeys: [],
|
||||
},
|
||||
});
|
||||
cloudlyUrl: 'https://example.com:8443',
|
||||
servers: [],
|
||||
sshKeys: [],
|
||||
};
|
||||
|
||||
console.log('Cluster added:', newCluster);
|
||||
// Store the newly created cluster with Cloudly
|
||||
const storedCluster = await myCloudlyInstance.clusterManager.storeCluster(newCluster);
|
||||
console.log('Cluster stored:', storedCluster);
|
||||
}
|
||||
main();
|
||||
|
||||
manageClusters();
|
||||
```
|
||||
|
||||
### Example: Manage Cloudflare DNS Records
|
||||
In this scenario, a cluster called `example_cluster` is initialized using the `Cloudly` instance. This method represents a central mechanism to efficiently handle cluster entities and associated metadata.
|
||||
|
||||
#### Integrating With Cloudflare for DNS Management
|
||||
|
||||
`@serve.zone/cloudly` provides built-in capabilities for managing DNS records through integration with Cloudflare. Using the `CloudflareConnector`, you can programmatically create, manage, and delete DNS entries:
|
||||
|
||||
```typescript
|
||||
import { Cloudly, CloudflareConnector } from '@serve.zone/cloudly';
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
|
||||
async function manageDNSRecords() {
|
||||
async function configureCloudflareDNS() {
|
||||
const myCloudlyConfig = {
|
||||
cfToken: 'your_cloudflare_api_token',
|
||||
environment: 'development',
|
||||
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||
publicUrl: 'example.com',
|
||||
publicPort: 8443,
|
||||
hetznerToken: 'your_hetzner_api_token',
|
||||
mongoDescriptor: {
|
||||
mongoDbUrl: 'mongodb+srv://<username>:<password>@<cluster>.mongodb.net/myFirstDatabase',
|
||||
mongoDbName: 'myDatabase',
|
||||
mongoDbUser: 'myUser',
|
||||
mongoDbPass: 'myPassword',
|
||||
},
|
||||
cfToken: 'your_cloudflare_api_token',
|
||||
environment: 'development',
|
||||
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||
publicUrl: 'example.com',
|
||||
publicPort: 8443,
|
||||
hetznerToken: 'your_hetzner_api_token'
|
||||
};
|
||||
|
||||
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
|
||||
await myCloudlyInstance.start();
|
||||
|
||||
const dnsInfo = {
|
||||
zoneName: 'example.com',
|
||||
recordName: 'sub.example.com',
|
||||
recordType: 'A',
|
||||
recordContent: '127.0.0.1',
|
||||
};
|
||||
|
||||
const cfConnector = myCloudlyInstance.cloudflareConnector.cloudflare;
|
||||
const newRecord = await cfConnector.createDNSRecord(
|
||||
dnsInfo.zoneName,
|
||||
dnsInfo.recordName,
|
||||
dnsInfo.recordType,
|
||||
dnsInfo.recordContent
|
||||
);
|
||||
|
||||
console.log('DNS Record created:', newRecord);
|
||||
const dnsRecord = await cfConnector.createDNSRecord('example.com', 'sub.example.com', 'A', '127.0.0.1');
|
||||
console.log('DNS Record:', dnsRecord);
|
||||
}
|
||||
manageDNSRecords();
|
||||
|
||||
configureCloudflareDNS();
|
||||
```
|
||||
|
||||
### Example: Integrate with DigitalOcean
|
||||
Here, you create an A record for the subdomain `sub.example.com` pointing to `127.0.0.1`. All communication with Cloudflare is handled directly through the interface without manual intervention.
|
||||
|
||||
#### Dynamic Interaction with DigitalOcean
|
||||
|
||||
DigitalOcean resource management, including droplet creation, is simplified in Cloudly. By extending the API to encapsulate calls to external providers, Cloudly provides a seamless experience:
|
||||
|
||||
```typescript
|
||||
import { Cloudly, DigitalOceanConnector } from '@serve.zone/cloudly';
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
|
||||
async function manageDroplet() {
|
||||
async function createDigitalOceanDroplets() {
|
||||
const myCloudlyConfig = {
|
||||
cfToken: 'your_cloudflare_api_token',
|
||||
environment: 'development',
|
||||
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||
publicUrl: 'example.com',
|
||||
publicPort: 8443,
|
||||
hetznerToken: 'your_hetzner_api_token',
|
||||
mongoDescriptor: {
|
||||
mongoDbUrl: 'mongodb+srv://<username>:<password>@<cluster>.mongodb.net/myFirstDatabase',
|
||||
mongoDbName: 'myDatabase',
|
||||
mongoDbUser: 'myUser',
|
||||
mongoDbPass: 'myPassword',
|
||||
},
|
||||
cfToken: 'your_cloudflare_api_token',
|
||||
environment: 'development',
|
||||
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||
publicUrl: 'example.com',
|
||||
publicPort: 8443,
|
||||
hetznerToken: 'your_hetzner_api_token'
|
||||
};
|
||||
|
||||
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
|
||||
await myCloudlyInstance.start();
|
||||
|
||||
const doConnector = myCloudlyInstance.digitaloceanConnector;
|
||||
const dropletInfo = {
|
||||
name: 'example-droplet',
|
||||
region: 'nyc3',
|
||||
size: 's-1vcpu-1gb',
|
||||
image: 'ubuntu-20-04-x64',
|
||||
};
|
||||
|
||||
const newDroplet = await doConnector.createDroplet(
|
||||
dropletInfo.name,
|
||||
dropletInfo.region,
|
||||
dropletInfo.size,
|
||||
dropletInfo.image
|
||||
);
|
||||
|
||||
console.log('Droplet created:', newDroplet);
|
||||
const droplet = await doConnector.createDroplet('example-droplet', 'nyc3', 's-1vcpu-1gb', 'ubuntu-20-04-x64');
|
||||
console.log('Droplet created:', droplet);
|
||||
}
|
||||
manageDroplet();
|
||||
|
||||
createDigitalOceanDroplets();
|
||||
```
|
||||
|
||||
### Using Cloudly Web Interface
|
||||
If your project includes a web interface to manage various sections like DNS, deployments, clusters, etc., you can use the provided elements and state management. Below is an example of setting up a dashboard using the components defined:
|
||||
In this script, a droplet named `example-droplet` is created within the `nyc3` region using the `ubuntu-20-04-x64` image. The module abstracts complexities by directly interfacing with DigitalOcean.
|
||||
|
||||
### Advanced Use Cases
|
||||
|
||||
#### Implementing Web Management Interface
|
||||
|
||||
`@serve.zone/cloudly` facilitates dashboard management with advanced Web Components built with `@design.estate`. This section of the library allows the creation of dynamic, interactive panels for real-time resource management in a modern browser interface.
|
||||
|
||||
```typescript
|
||||
import { commitinfo } from '../00_commitinfo_data.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
import { html } from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
css,
|
||||
cssManager,
|
||||
customElement,
|
||||
html,
|
||||
state
|
||||
} from '@design.estate/dees-element';
|
||||
import { CloudlyViewBackups } from './cloudly-view-backups.js';
|
||||
import { CloudlyViewClusters } from './cloudly-view-clusters.js';
|
||||
import { CloudlyViewDbs } from './cloudly-view-dbs.js';
|
||||
import { CloudlyViewDeployments } from './cloudly-view-deployments.js';
|
||||
import { CloudlyViewDns } from './cloudly-view-dns.js';
|
||||
import { CloudlyViewImages } from './cloudly-view-images.js';
|
||||
import { CloudlyViewLogs } from './cloudly-view-logs.js';
|
||||
import { CloudlyViewMails } from './cloudly-view-mails.js';
|
||||
import { CloudlyViewOverview } from './cloudly-view-overview.js';
|
||||
import { CloudlyViewS3 } from './cloudly-view-s3.js';
|
||||
import { CloudlyViewSecretBundles } from './cloudly-view-secretbundles.js';
|
||||
import { CloudlyViewSecretGroups } from './cloudly-view-secretgroups.js';
|
||||
import { CloudlyViewServices } from './cloudly-view-services.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'cvault-dashboard': CloudlyDashboard;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('cloudly-dashboard')
|
||||
export class CloudlyDashboard extends DeesElement {
|
||||
@state() private jwt: string;
|
||||
@state() private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
clusters: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subcription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
this.rxSubscriptions.push(subcription);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
.maincontainer {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
font-family: 'Cal Sans';
|
||||
}
|
||||
`,
|
||||
];
|
||||
public render() {
|
||||
const renderDashboard = () => {
|
||||
return html`
|
||||
<div class="maincontainer">
|
||||
<dees-simple-login name="cloudly v${commitinfo.version}">
|
||||
<dees-simple-appdash name="cloudly v${commitinfo.version}"
|
||||
.viewTabs=${[
|
||||
{
|
||||
name: 'Overview',
|
||||
element: CloudlyViewOverview,
|
||||
},
|
||||
{
|
||||
name: 'SecretGroups',
|
||||
element: CloudlyViewSecretGroups,
|
||||
},
|
||||
{
|
||||
name: 'SecretBundles',
|
||||
element: CloudlyViewSecretBundles,
|
||||
},
|
||||
{
|
||||
name: 'Clusters',
|
||||
element: CloudlyViewClusters,
|
||||
},
|
||||
{
|
||||
name: 'Images',
|
||||
element: CloudlyViewImages,
|
||||
},
|
||||
{
|
||||
name: 'Services',
|
||||
element: CloudlyViewServices,
|
||||
},
|
||||
{
|
||||
name: 'Deployments',
|
||||
element: CloudlyViewDeployments,
|
||||
},
|
||||
{
|
||||
name: 'DNS',
|
||||
element: CloudlyViewDns,
|
||||
},
|
||||
{
|
||||
name: 'Mails',
|
||||
element: CloudlyViewMails,
|
||||
},
|
||||
{
|
||||
name: 'Logs',
|
||||
element: CloudlyViewLogs,
|
||||
},
|
||||
{
|
||||
name: 's3',
|
||||
element: CloudlyViewS3,
|
||||
},
|
||||
{
|
||||
name: 'DBs',
|
||||
element: CloudlyViewDbs,
|
||||
},
|
||||
{
|
||||
name: 'Backups',
|
||||
element: CloudlyViewBackups,
|
||||
},
|
||||
] as plugins.deesCatalog.IView[]}
|
||||
></dees-simple-appdash>
|
||||
</dees-simple-login>
|
||||
</div>
|
||||
<cloudly-dashboard>
|
||||
<dees-simple-appdash>
|
||||
<!-- Define sections and elements -->
|
||||
<cloudly-view-clusters></cloudly-view-clusters>
|
||||
<cloudly-view-dns></cloudly-view-dns>
|
||||
<cloudly-view-images></cloudly-view-images>
|
||||
<!-- Other custom views -->
|
||||
</dees-simple-appdash>
|
||||
</cloudly-dashboard>
|
||||
`;
|
||||
}
|
||||
public async firstUpdated() {
|
||||
const simpleLogin = this.shadowRoot.querySelector('dees-simple-login');
|
||||
simpleLogin.addEventListener('login', (e: CustomEvent) => {
|
||||
console.log(e.detail);
|
||||
this.login(e.detail.data.username, e.detail.data.password);
|
||||
});
|
||||
this.addEventListener('contextmenu', (eventArg) => {
|
||||
plugins.deesCatalog.DeesContextmenu.openContextMenuWithOptions(eventArg, [
|
||||
{
|
||||
name: 'About',
|
||||
iconName: 'mugHot',
|
||||
action: async () => {
|
||||
await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'About',
|
||||
content: html`configvault ${commitinfo.version}`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'close',
|
||||
iconName: null,
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
};
|
||||
|
||||
// lets deal with initial state
|
||||
const domtools = await this.domtoolsPromise;
|
||||
const loginState = appstate.loginStatePart.getState();
|
||||
console.log(loginState);
|
||||
if (loginState.jwt) {
|
||||
this.jwt = loginState.jwt;
|
||||
await simpleLogin.switchToSlottedContent();
|
||||
await appstate.dataState.dispatchAction(appstate.getDataAction, null);
|
||||
}
|
||||
}
|
||||
|
||||
private async login(username: string, password: string) {
|
||||
console.log(`attempting to login...`);
|
||||
const simpleLogin = this.shadowRoot.querySelector('dees-simple-login');
|
||||
const form = simpleLogin.shadowRoot.querySelector('dees-form');
|
||||
form.setStatus('pending', 'Logging in...');
|
||||
const state = await appstate.loginStatePart.dispatchAction(appstate.loginAction, {
|
||||
username,
|
||||
password,
|
||||
});
|
||||
if (state.jwt) {
|
||||
console.log('got jwt');
|
||||
this.jwt = state.jwt;
|
||||
form.setStatus('success', 'Logged in!');
|
||||
await simpleLogin.switchToSlottedContent();
|
||||
await appstate.dataState.dispatchAction(appstate.getDataAction, null);
|
||||
} else {
|
||||
form.setStatus('error', 'Login failed!');
|
||||
await domtools.convenience.smartdelay.delayFor(2000);
|
||||
form.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private async logout() {}
|
||||
}
|
||||
document.body.appendChild(renderDashboard());
|
||||
```
|
||||
|
||||
This script sets up a cloud management dashboard for interacting with various cloud services seamlessly. It covers creating clusters, managing DNS records, handling cloud-provider-specific resources, and much more.
|
||||
Utilizing the custom web components designed specifically for Cloudly, dashboards are adaptable, interactive, and maintainable. These elements allow you to structure a complete cloud management center without needing to delve into detailed UI engineering.
|
||||
|
||||
With the examples provided above, you should now have a good understanding of how to use `@serve.zone/cloudly` to manage your cloud infrastructure programmatically. For deeper insights and additional features, refer to the documentation relevant to specific modules and methods used in your application.
|
||||
#### Comprehensive Log Management
|
||||
|
||||
With Cloudly’s Log Management capabilities, you can track and analyze system logs for better insights into your cloud ecosystem’s behavior:
|
||||
|
||||
```typescript
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
|
||||
async function initiateLogManagement() {
|
||||
const myCloudlyConfig = {
|
||||
cfToken: 'your_cloudflare_api_token',
|
||||
environment: 'development',
|
||||
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||
publicUrl: 'example.com',
|
||||
publicPort: 8443,
|
||||
hetznerToken: 'your_hetzner_api_token',
|
||||
mongoDescriptor: {
|
||||
mongoDbUrl: 'mongodb+srv://<username>:<password>@<cluster>.mongodb.net/myFirstDatabase',
|
||||
mongoDbName: 'myDatabase',
|
||||
mongoDbUser: 'myUser',
|
||||
mongoDbPass: 'myPassword',
|
||||
},
|
||||
};
|
||||
|
||||
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
|
||||
await myCloudlyInstance.start();
|
||||
|
||||
const logs = await myCloudlyInstance.logManager.fetchLogs();
|
||||
console.log('Logs:', logs);
|
||||
}
|
||||
|
||||
initiateLogManagement();
|
||||
```
|
||||
|
||||
Cloudly provides the tools needed to collect and process logs within your cloud infrastructure. Logs are an essential part of system validation, troubleshooting, monitoring, and auditing.
|
||||
|
||||
#### Secret Management and Bundles
|
||||
|
||||
Managing secrets securely and efficiently is critical for cloud operations. Cloudly allows you to create and manage secret groups and bundles that can be used across multiple applications and environments:
|
||||
|
||||
```typescript
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
|
||||
async function createSecrets() {
|
||||
const myCloudlyConfig = {
|
||||
cfToken: 'your_cloudflare_api_token',
|
||||
environment: 'development',
|
||||
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||
publicUrl: 'example.com',
|
||||
publicPort: 8443,
|
||||
hetznerToken: 'your_hetzner_api_token',
|
||||
mongoDescriptor: {
|
||||
mongoDbUrl: 'mongodb+srv://<username>:<password>@<cluster>.mongodb.net/myFirstDatabase',
|
||||
mongoDbName: 'myDatabase',
|
||||
mongoDbUser: 'myUser',
|
||||
mongoDbPass: 'myPassword',
|
||||
},
|
||||
};
|
||||
|
||||
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
|
||||
await myCloudlyInstance.start();
|
||||
|
||||
const newSecretGroup = await myCloudlyInstance.secretManager.createSecretGroup({
|
||||
name: 'example_secret_group',
|
||||
secrets: [
|
||||
{ key: 'SECRET_KEY', value: 's3cr3t' },
|
||||
],
|
||||
});
|
||||
|
||||
const newSecretBundle = await myCloudlyInstance.secretManager.createSecretBundle({
|
||||
name: 'example_bundle',
|
||||
secretGroups: [newSecretGroup],
|
||||
});
|
||||
|
||||
console.log('Created Secret Group and Bundle:', newSecretGroup, newSecretBundle);
|
||||
}
|
||||
|
||||
createSecrets();
|
||||
```
|
||||
|
||||
Secrets, such as API keys and sensitive configuration data, are managed efficiently using secret groups and bundles. This structured approach to secret management enhances both security and accessibility.
|
||||
|
||||
### Task Scheduling and Management
|
||||
|
||||
With task buffers, you can schedule and manage background tasks integral to cloud operations:
|
||||
|
||||
```typescript
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
import { TaskBuffer } from '@push.rocks/taskbuffer';
|
||||
|
||||
async function scheduleTasks() {
|
||||
const myCloudlyConfig = {
|
||||
cfToken: 'your_cloudflare_api_token',
|
||||
environment: 'development',
|
||||
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||
publicUrl: 'example.com',
|
||||
publicPort: 8443,
|
||||
hetznerToken: 'your_hetzner_api_token',
|
||||
mongoDescriptor: {
|
||||
mongoDbUrl: 'mongodb+srv://<username>:<password>@<cluster>.mongodb.net/myFirstDatabase',
|
||||
mongoDbName: 'myDatabase',
|
||||
mongoDbUser: 'myUser',
|
||||
mongoDbPass: 'myPassword',
|
||||
},
|
||||
};
|
||||
|
||||
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
|
||||
await myCloudlyInstance.start();
|
||||
|
||||
const taskManager = new TaskBuffer();
|
||||
taskManager.scheduleEvery('minute', async () => {
|
||||
console.log('Running scheduled task...');
|
||||
// Task logic
|
||||
});
|
||||
|
||||
console.log('Tasks scheduled.');
|
||||
}
|
||||
|
||||
scheduleTasks();
|
||||
```
|
||||
|
||||
The example demonstrates setting up periodic task execution using task buffers as part of Cloudly's task management. Whether it's maintenance routines, data updates, or resource checks, tasks can be managed effectively.
|
||||
|
||||
This comprehensive overview of `@serve.zone/cloudly` is designed to help you leverage its full capabilities in managing multi-cloud environments. Each example is meant to serve as a starting point, and you are encouraged to explore further by consulting the relevant sections in the documentation, engaging with community discussions, or experimenting in your own environment.
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
|
50
test/helpers/cloudlyfactory.ts
Normal file
50
test/helpers/cloudlyfactory.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { Qenv } from '@push.rocks/qenv';
|
||||
const testQenv = new Qenv('./', './.nogit/');
|
||||
|
||||
import * as cloudly from '../../ts/index.js';
|
||||
|
||||
const stopFunctions: Array<() => Promise<void>> = [];
|
||||
|
||||
const tapToolsNodeMod = await import('@push.rocks/tapbundle/node');
|
||||
const smartmongo = await tapToolsNodeMod.tapNodeTools.createSmartmongo();
|
||||
stopFunctions.push(async () => {
|
||||
await smartmongo.stopAndDumpToDir('./.nogit/mongodump');
|
||||
});
|
||||
const smarts3 = await tapToolsNodeMod.tapNodeTools.createSmarts3();
|
||||
await smarts3.createBucket('cloudly-test');
|
||||
stopFunctions.push(async () => {
|
||||
await smarts3.stop();
|
||||
});
|
||||
|
||||
export const testCloudlyConfig: cloudly.ICloudlyConfig = {
|
||||
cfToken: await testQenv.getEnvVarOnDemand('CF_TOKEN'),
|
||||
environment: 'integration',
|
||||
letsEncryptEmail: 'test@serve.zone',
|
||||
publicUrl: '127.0.0.1',
|
||||
publicPort: '8080',
|
||||
mongoDescriptor: await smartmongo.getMongoDescriptor(),
|
||||
s3Descriptor: await smarts3.getS3Descriptor(),
|
||||
sslMode: 'none',
|
||||
...(() => {
|
||||
if (process.env.NPMCI_SECRET01) {
|
||||
return {
|
||||
hetznerToken: process.env.NPMCI_SECRET01,
|
||||
};
|
||||
}
|
||||
})(),
|
||||
};
|
||||
|
||||
await tapToolsNodeMod.tapNodeTools.testFileProvider.getDockerAlpineImageAsLocalTarball();
|
||||
|
||||
export const createCloudly = async () => {
|
||||
const cloudlyInstance = new cloudly.Cloudly(testCloudlyConfig);
|
||||
return cloudlyInstance;
|
||||
};
|
||||
|
||||
export const stopCloudly = async () => {
|
||||
await Promise.all(stopFunctions.map((stopFunction) => stopFunction()));
|
||||
};
|
||||
|
||||
export const getEnvVarOnDemand = async (envVarName: string) => {
|
||||
return testQenv.getEnvVarOnDemand(envVarName);
|
||||
};
|
9
test/helpers/docker.ts
Normal file
9
test/helpers/docker.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import * as smartstream from '@push.rocks/smartstream';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
|
||||
export const getAlpineImageReadableStream = async () => {
|
||||
const currentDir = smartpath.get.dirnameFromImportMetaUrl(import.meta.url);
|
||||
const imagePath = smartpath.join(currentDir, '../../.nogit/testfiles/alpine.tar');
|
||||
const readableStream = smartstream.nodewebhelpers.createWebReadableStreamFromFile(imagePath);
|
||||
return readableStream;
|
||||
}
|
2
test/helpers/index.ts
Normal file
2
test/helpers/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './cloudlyfactory.js';
|
||||
export * from './docker.js';
|
81
test/test.apiclient.ts
Normal file
81
test/test.apiclient.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import * as helpers from './helpers/index.js';
|
||||
|
||||
import * as cloudly from '../ts/index.js';
|
||||
import * as cloudlyApiClient from '../ts_apiclient/index.js';
|
||||
import { Image } from '../ts_apiclient/classes.image.js';
|
||||
|
||||
let testCloudly: cloudly.Cloudly;
|
||||
let testClient: cloudlyApiClient.CloudlyApiClient;
|
||||
|
||||
tap.preTask('should start cloudly', async () => {
|
||||
testCloudly = await helpers.createCloudly();
|
||||
await testCloudly.start();
|
||||
});
|
||||
|
||||
tap.preTask('should create a new machine user for testing', async () => {
|
||||
const machineUser = new testCloudly.authManager.CUser();
|
||||
machineUser.id = await testCloudly.authManager.CUser.getNewId();
|
||||
machineUser.data = {
|
||||
type: 'machine',
|
||||
username: 'test',
|
||||
password: 'test',
|
||||
tokens: [{
|
||||
token: 'test',
|
||||
expiresAt: Date.now() + 3600 * 1000 * 24 * 365,
|
||||
assignedRoles: ['admin'],
|
||||
}],
|
||||
role: 'admin',
|
||||
};
|
||||
await machineUser.save();
|
||||
});
|
||||
|
||||
tap.test('should create a new cloudlyApiClient', async () => {
|
||||
testClient = new cloudlyApiClient.CloudlyApiClient({
|
||||
registerAs: 'api',
|
||||
cloudlyUrl: `http://${helpers.testCloudlyConfig.publicUrl}:${helpers.testCloudlyConfig.publicPort}`,
|
||||
});
|
||||
await testClient.start();
|
||||
expect(testClient).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('create a new machine user', async () => {
|
||||
const machineUser = await testCloudly.authManager.CUser.createMachineUser('test', 'api');
|
||||
machineUser.data.tokens.push({
|
||||
token: 'test',
|
||||
expiresAt: Date.now() + 3600 * 1000 * 24 * 365,
|
||||
assignedRoles: ['api'],
|
||||
})
|
||||
await machineUser.save();
|
||||
})
|
||||
|
||||
tap.test('should get an identity', async () => {
|
||||
const identity = await testClient.getIdentityByToken('test');
|
||||
expect(identity).toBeTruthy();
|
||||
console.log(identity);
|
||||
});
|
||||
|
||||
let image: Image;
|
||||
tap.test('should create and upload an image', async () => {
|
||||
image = await testClient.image.createImage({
|
||||
name: 'test',
|
||||
description: 'test'
|
||||
});
|
||||
console.log('created image: ', image);
|
||||
expect(image).toBeInstanceOf(Image);
|
||||
})
|
||||
|
||||
tap.test('should upload an image version', async () => {
|
||||
const imageStream = await helpers.getAlpineImageReadableStream();
|
||||
|
||||
await image.pushImageVersion('v1.0.0', imageStream);
|
||||
});
|
||||
|
||||
tap.test('should stop the apiclient', async (toolsArg) => {
|
||||
await toolsArg.delayFor(10000);
|
||||
await helpers.stopCloudly();
|
||||
await testClient.stop();
|
||||
await testCloudly.stop();
|
||||
})
|
||||
|
||||
export default tap.start();
|
26
test/test.ts
26
test/test.ts
@ -1,29 +1,11 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import { Qenv } from '@push.rocks/qenv';
|
||||
const testQenv = new Qenv('./', './.nogit/');
|
||||
process.env.TESTING_CLOUDLY = 'true';
|
||||
|
||||
delete process.env.CLI_CALL;
|
||||
import * as helpers from './helpers/index.js';
|
||||
|
||||
import * as cloudly from '../ts/index.js';
|
||||
|
||||
let testCloudly: cloudly.Cloudly;
|
||||
tap.test('first test', async () => {
|
||||
const cloudlyConfig: cloudly.ICloudlyConfig = {
|
||||
cfToken: testQenv.getEnvVarOnDemand('CF_TOKEN'),
|
||||
environment: 'integration',
|
||||
letsEncryptEmail: testQenv.getEnvVarOnDemand('LETSENCRYPT_EMAIL'),
|
||||
publicUrl: testQenv.getEnvVarOnDemand('SERVEZONE_URL'),
|
||||
publicPort: testQenv.getEnvVarOnDemand('SERVEZONE_PORT'),
|
||||
mongoDescriptor: {
|
||||
mongoDbName: testQenv.getEnvVarOnDemand('MONGODB_DATABASE'),
|
||||
mongoDbUser: testQenv.getEnvVarOnDemand('MONGODB_USER'),
|
||||
mongoDbPass: testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'),
|
||||
mongoDbUrl: testQenv.getEnvVarOnDemand('MONGODB_URL'),
|
||||
},
|
||||
digitalOceanToken: testQenv.getEnvVarOnDemand('DIGITALOCEAN_TOKEN'),
|
||||
};
|
||||
testCloudly = new cloudly.Cloudly(cloudlyConfig);
|
||||
testCloudly = await helpers.createCloudly();
|
||||
expect(testCloudly).toBeInstanceOf(cloudly.Cloudly);
|
||||
});
|
||||
|
||||
@ -32,8 +14,10 @@ tap.test('should init cloudly', async () => {
|
||||
});
|
||||
|
||||
tap.test('should end the service', async (tools) => {
|
||||
await tools.delayFor(5000);
|
||||
await helpers.stopCloudly();
|
||||
await testCloudly.stop();
|
||||
tools.delayFor(1000).then(() => process.exit())
|
||||
tools.delayFor(1000).then(() => process.exit());
|
||||
});
|
||||
|
||||
tap.start();
|
||||
|
@ -1,8 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @pushrocks/commitinfo
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/cloudly',
|
||||
version: '1.0.216',
|
||||
description: 'A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.'
|
||||
version: '5.0.4',
|
||||
description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.'
|
||||
}
|
||||
|
13
ts/00demo/demo.data.images.ts
Normal file
13
ts/00demo/demo.data.images.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export const demoImages: plugins.servezoneInterfaces.data.IImage[] = [
|
||||
{
|
||||
id: 'DemoImage1',
|
||||
data: {
|
||||
name: 'DemoImage1',
|
||||
description: 'DemoImage1',
|
||||
versions: [],
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -63,6 +63,8 @@ for (let i = 0; i < demoSecretGroups.length; i++) {
|
||||
id: `configBundleId${i + 1}`,
|
||||
data: {
|
||||
name: `Demo Config Bundle ${i + 1}`,
|
||||
imageClaims: [],
|
||||
type: 'external',
|
||||
description: 'Demo Purpose',
|
||||
includedSecretGroupIds: [secretGroup.id],
|
||||
includedTags: secretGroup.data.tags,
|
20
ts/00demo/demo.data.users.ts
Normal file
20
ts/00demo/demo.data.users.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import type { Cloudly } from '../classes.cloudly.js';
|
||||
|
||||
export const getUsers = async (cloudlyRef: Cloudly) => {
|
||||
const users: plugins.servezoneInterfaces.data.IUser[] = [];
|
||||
const envAdminUser = await cloudlyRef.config.appData.waitForAndGetKey('servezoneAdminaccount');
|
||||
if (envAdminUser) {
|
||||
users.push({
|
||||
id: 'envadmin',
|
||||
data: {
|
||||
type: 'human',
|
||||
username: envAdminUser.split(':')[0],
|
||||
password: envAdminUser.split(':')[1],
|
||||
role: 'admin',
|
||||
},
|
||||
});
|
||||
}
|
||||
return users;
|
||||
};
|
@ -35,4 +35,31 @@ export const installDemoData = async (cloudlyRef: Cloudly) => {
|
||||
await cluster.delete();
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
// USERS
|
||||
const users = await cloudlyRef.authManager.CUser.getInstances({});
|
||||
for (const user of users) {
|
||||
await user.delete();
|
||||
}
|
||||
|
||||
const demoDataUsers = await import('./demo.data.users.js');
|
||||
for (const user of await demoDataUsers.getUsers(cloudlyRef)) {
|
||||
const userInstance = new cloudlyRef.authManager.CUser();
|
||||
Object.assign(userInstance, user);
|
||||
await userInstance.save();
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
// IMAGES
|
||||
const images = await cloudlyRef.imageManager.CImage.getInstances({});
|
||||
for (const image of images) {
|
||||
await image.delete();
|
||||
}
|
||||
|
||||
const demoDataImages = await import('./demo.data.images.js');
|
||||
for (const image of demoDataImages.demoImages) {
|
||||
const imageInstance = new cloudlyRef.imageManager.CImage();
|
||||
Object.assign(imageInstance, image);
|
||||
await imageInstance.save();
|
||||
}
|
||||
}
|
@ -15,13 +15,15 @@ import { MongodbConnector } from './connector.mongodb/connector.js';
|
||||
|
||||
// processes
|
||||
import { CloudlyCoreflowManager } from './manager.coreflow/coreflowmanager.js';
|
||||
import { ClusterManager } from './manager.cluster/clustermanager.js';
|
||||
import { ClusterManager } from './manager.cluster/classes.clustermanager.js';
|
||||
import { CloudlyTaskmanager } from './manager.task/taskmanager.js';
|
||||
import { CloudlySecretManager } from './manager.secret/classes.secretmanager.js'
|
||||
import { CloudlyServerManager } from './manager.server/servermanager.js';
|
||||
import { CloudlySecretManager } from './manager.secret/classes.secretmanager.js';
|
||||
import { CloudlyServerManager } from './manager.server/classes.servermanager.js';
|
||||
import { ExternalApiManager } from './manager.status/statusmanager.js';
|
||||
import { ExternalRegistryManager } from './manager.externalregistry/index.js';
|
||||
import { ImageManager } from './manager.image/classes.imagemanager.js';
|
||||
import { logger } from './cloudly.logging.js';
|
||||
import { logger } from './logger.js';
|
||||
import { CloudlyAuthManager } from './manager.auth/classes.authmanager.js';
|
||||
|
||||
/**
|
||||
* Cloudly class can be used to instantiate a cloudly server.
|
||||
@ -48,17 +50,21 @@ export class Cloudly {
|
||||
public mongodbConnector: MongodbConnector;
|
||||
|
||||
// managers
|
||||
public authManager: CloudlyAuthManager;
|
||||
public secretManager: CloudlySecretManager;
|
||||
public clusterManager: ClusterManager;
|
||||
public coreflowManager: CloudlyCoreflowManager;
|
||||
public externalApiManager: ExternalApiManager;
|
||||
public externalRegistryManager: ExternalRegistryManager;
|
||||
public imageManager: ImageManager;
|
||||
public taskManager: CloudlyTaskmanager;
|
||||
public serverManager: CloudlyServerManager;
|
||||
|
||||
private readyDeferred = new plugins.smartpromise.Deferred();
|
||||
|
||||
constructor() {
|
||||
private configOptions: plugins.servezoneInterfaces.data.ICloudlyConfig;
|
||||
constructor(configArg?: plugins.servezoneInterfaces.data.ICloudlyConfig) {
|
||||
this.configOptions = configArg;
|
||||
this.cloudlyInfo = new CloudlyInfo(this);
|
||||
this.config = new CloudlyConfig(this);
|
||||
|
||||
@ -71,10 +77,12 @@ export class Cloudly {
|
||||
this.cloudflareConnector = new CloudflareConnector(this);
|
||||
this.letsencryptConnector = new LetsencryptConnector(this);
|
||||
|
||||
// processes
|
||||
// managers
|
||||
this.authManager = new CloudlyAuthManager(this);
|
||||
this.clusterManager = new ClusterManager(this);
|
||||
this.coreflowManager = new CloudlyCoreflowManager(this);
|
||||
this.externalApiManager = new ExternalApiManager(this);
|
||||
this.externalRegistryManager = new ExternalRegistryManager(this);
|
||||
this.imageManager = new ImageManager(this);
|
||||
this.taskManager = new CloudlyTaskmanager(this);
|
||||
this.secretManager = new CloudlySecretManager(this);
|
||||
@ -87,9 +95,10 @@ export class Cloudly {
|
||||
*/
|
||||
public async start() {
|
||||
// config
|
||||
await this.config.init();
|
||||
await this.config.init(this.configOptions);
|
||||
|
||||
// manageers
|
||||
await this.authManager.start();
|
||||
await this.secretManager.start();
|
||||
await this.serverManager.start();
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { logger } from './cloudly.logging.js';
|
||||
import { logger } from './logger.js';
|
||||
import type { Cloudly } from './classes.cloudly.js';
|
||||
|
||||
/**
|
||||
@ -9,66 +9,57 @@ import type { Cloudly } from './classes.cloudly.js';
|
||||
export class CloudlyConfig {
|
||||
public cloudlyRef: Cloudly;
|
||||
public appData: plugins.npmextra.AppData<plugins.servezoneInterfaces.data.ICloudlyConfig>;
|
||||
public data: plugins.servezoneInterfaces.data.ICloudlyConfig
|
||||
|
||||
// authentication and settings
|
||||
public smartjwtInstance: plugins.smartjwt.SmartJwt;
|
||||
|
||||
public data: plugins.servezoneInterfaces.data.ICloudlyConfig;
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
}
|
||||
|
||||
public async init() {
|
||||
this.appData = await plugins.npmextra.AppData.createAndInit<plugins.servezoneInterfaces.data.ICloudlyConfig>({
|
||||
envMapping: {
|
||||
cfToken: 'CF_TOKEN',
|
||||
environment: 'SERVEZONE_ENVIRONMENT' as 'production' | 'integration',
|
||||
letsEncryptEmail: 'hard:domains@lossless.org',
|
||||
hetznerToken: 'HETZNER_API_TOKEN',
|
||||
letsEncryptPrivateKey: null,
|
||||
publicUrl: 'SERVEZONE_URL',
|
||||
publicPort: 'SERVEZONE_PORT',
|
||||
mongoDescriptor: {
|
||||
mongoDbUrl: 'MONGODB_URL',
|
||||
mongoDbName: 'MONGODB_DATABASE',
|
||||
mongoDbUser: 'MONGODB_USER',
|
||||
mongoDbPass: 'MONGODB_PASSWORD',
|
||||
public async init(configArg?: plugins.servezoneInterfaces.data.ICloudlyConfig) {
|
||||
this.appData =
|
||||
await plugins.npmextra.AppData.createAndInit<plugins.servezoneInterfaces.data.ICloudlyConfig>(
|
||||
{
|
||||
envMapping: {
|
||||
cfToken: 'CF_TOKEN',
|
||||
environment: 'SERVEZONE_ENVIRONMENT' as 'production' | 'integration',
|
||||
letsEncryptEmail: 'hard:domains@lossless.org',
|
||||
hetznerToken: 'HETZNER_API_TOKEN',
|
||||
letsEncryptPrivateKey: null,
|
||||
publicUrl: 'SERVEZONE_URL',
|
||||
publicPort: 'SERVEZONE_PORT',
|
||||
mongoDescriptor: {
|
||||
mongoDbUrl: 'MONGODB_URL',
|
||||
mongoDbName: 'MONGODB_DATABASE',
|
||||
mongoDbUser: 'MONGODB_USER',
|
||||
mongoDbPass: 'MONGODB_PASSWORD',
|
||||
},
|
||||
s3Descriptor: {
|
||||
endpoint: 'S3_ENDPOINT',
|
||||
accessKey: 'S3_ACCESSKEY',
|
||||
accessSecret: 'S3_SECRETKEY',
|
||||
port: 'S3_PORT', // Note: This will remain as a string. Ensure to parse it to an integer where it's used.
|
||||
useSsl: true,
|
||||
},
|
||||
sslMode:
|
||||
'SERVEZONE_SSLMODE' as plugins.servezoneInterfaces.data.ICloudlyConfig['sslMode'],
|
||||
servezoneAdminaccount: 'SERVEZONE_ADMINACCOUNT',
|
||||
},
|
||||
requiredKeys: [
|
||||
'cfToken',
|
||||
'hetznerToken',
|
||||
'letsEncryptEmail',
|
||||
'publicUrl',
|
||||
'publicPort',
|
||||
'sslMode',
|
||||
'environment',
|
||||
'mongoDescriptor',
|
||||
],
|
||||
overwriteObject: configArg,
|
||||
},
|
||||
s3Descriptor: {
|
||||
endpoint: 'S3_ENDPOINT',
|
||||
accessKey: 'S3_ACCESSKEY',
|
||||
accessSecret: 'S3_SECRETKEY',
|
||||
port: 'S3_PORT', // Note: This will remain as a string. Ensure to parse it to an integer where it's used.
|
||||
useSsl: true,
|
||||
},
|
||||
sslMode: 'SERVEZONE_SSLMODE' as plugins.servezoneInterfaces.data.ICloudlyConfig['sslMode'],
|
||||
},
|
||||
requiredKeys: [
|
||||
'cfToken',
|
||||
'hetznerToken',
|
||||
'letsEncryptEmail',
|
||||
'publicUrl',
|
||||
'publicPort',
|
||||
'sslMode',
|
||||
'environment',
|
||||
'mongoDescriptor',
|
||||
],
|
||||
});
|
||||
);
|
||||
|
||||
this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
|
||||
const kvStore = await this.appData.getKvStore();
|
||||
|
||||
const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = await kvStore.readKey('jwtKeys');
|
||||
|
||||
if (!existingJwtKeys) {
|
||||
await this.smartjwtInstance.createNewKeyPair();
|
||||
const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson();
|
||||
await kvStore.writeKey('jwtKeys', newJwtKeys);
|
||||
} else {
|
||||
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
|
||||
}
|
||||
|
||||
this.data = await kvStore.readAll();
|
||||
const missingKeys = await this.appData.logMissingKeys();
|
||||
if (missingKeys.length > 0) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { Cloudly } from './classes.cloudly.js';
|
||||
import { logger } from './cloudly.logging.js';
|
||||
import { logger } from './logger.js';
|
||||
|
||||
/**
|
||||
* handles incoming requests from CI to deploy new versions of apps
|
||||
@ -10,7 +10,8 @@ export class CloudlyServer {
|
||||
/**
|
||||
* a reference to the cloudly instance
|
||||
*/
|
||||
private cloudlyRef: Cloudly;
|
||||
public cloudlyRef: Cloudly;
|
||||
public additionalHandlers: plugins.typedserver.servertools.Handler[] = [];
|
||||
|
||||
/**
|
||||
* the smartexpress server handling the actual requests
|
||||
@ -37,18 +38,24 @@ export class CloudlyServer {
|
||||
* init the reception instance
|
||||
*/
|
||||
public async start() {
|
||||
logger.log('info', `cloudly domain is ${this.cloudlyRef.config.data.publicUrl}`)
|
||||
logger.log('info', `cloudly domain is ${this.cloudlyRef.config.data.publicUrl}`);
|
||||
let sslCert: plugins.smartacme.Cert;
|
||||
|
||||
if (this.cloudlyRef.config.data.sslMode === 'letsencrypt') {
|
||||
logger.log('info', `Using letsencrypt for ssl mode. Trying to obtain a certificate...`)
|
||||
logger.log('info', `This might take 10 minutes...`)
|
||||
logger.log('info', `Using letsencrypt for ssl mode. Trying to obtain a certificate...`);
|
||||
logger.log('info', `This might take 10 minutes...`);
|
||||
sslCert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain(
|
||||
this.cloudlyRef.config.data.publicUrl
|
||||
this.cloudlyRef.config.data.publicUrl,
|
||||
);
|
||||
logger.log(
|
||||
'success',
|
||||
`Successfully obtained certificate for cloudly domain ${this.cloudlyRef.config.data.publicUrl}`,
|
||||
);
|
||||
logger.log('success', `Successfully obtained certificate for cloudly domain ${this.cloudlyRef.config.data.publicUrl}`)
|
||||
} else if (this.cloudlyRef.config.data.sslMode === 'external') {
|
||||
logger.log('info', `Using external certificate for ssl mode, meaning cloudly is not in charge of ssl termination.`)
|
||||
logger.log(
|
||||
'info',
|
||||
`Using external certificate for ssl mode, meaning cloudly is not in charge of ssl termination.`,
|
||||
);
|
||||
}
|
||||
|
||||
interface IRequestGuardData {
|
||||
@ -72,11 +79,13 @@ export class CloudlyServer {
|
||||
this.typedServer = new plugins.typedserver.TypedServer({
|
||||
cors: true,
|
||||
forceSsl: false,
|
||||
port: this.cloudlyRef.config.data.publicPort,
|
||||
...(sslCert ? {
|
||||
privateKey: sslCert.privateKey,
|
||||
publicKey: sslCert.publicKey,
|
||||
} : {}),
|
||||
port: this.cloudlyRef.config.data.publicPort,
|
||||
...(sslCert
|
||||
? {
|
||||
privateKey: sslCert.privateKey,
|
||||
publicKey: sslCert.publicKey,
|
||||
}
|
||||
: {}),
|
||||
injectReload: true,
|
||||
serveDir: paths.distServeDir,
|
||||
watch: true,
|
||||
@ -84,6 +93,10 @@ export class CloudlyServer {
|
||||
preferredCompressionMethod: 'gzip',
|
||||
});
|
||||
this.typedServer.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.typedServer.server.addRoute(
|
||||
'/curlfresh/:scriptname',
|
||||
this.cloudlyRef.serverManager.curlfreshInstance.handler,
|
||||
);
|
||||
await this.typedServer.start();
|
||||
}
|
||||
|
||||
|
@ -24,17 +24,20 @@ export class LetsencryptConnector {
|
||||
environment: this.cloudlyRef.config.data.environment,
|
||||
setChallenge: async (dnsChallenge) => {
|
||||
await this.cloudlyRef.cloudflareConnector.cloudflare.convenience.acmeSetDnsChallenge(
|
||||
dnsChallenge
|
||||
dnsChallenge,
|
||||
);
|
||||
},
|
||||
removeChallenge: async (dnsChallenge) => {
|
||||
await this.cloudlyRef.cloudflareConnector.cloudflare.convenience.acmeRemoveDnsChallenge(
|
||||
dnsChallenge
|
||||
dnsChallenge,
|
||||
);
|
||||
},
|
||||
mongoDescriptor: this.cloudlyRef.config.data.mongoDescriptor,
|
||||
});
|
||||
await this.smartacme.init();
|
||||
await this.smartacme.start().catch((err) => {
|
||||
console.error('error in init', err);
|
||||
console.log(`trying again in a few minutes`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,7 +11,9 @@ export class MongodbConnector {
|
||||
}
|
||||
|
||||
public async init() {
|
||||
this.smartdataDb = new plugins.smartdata.SmartdataDb(this.cloudlyRef.config.data.mongoDescriptor);
|
||||
this.smartdataDb = new plugins.smartdata.SmartdataDb(
|
||||
this.cloudlyRef.config.data.mongoDescriptor,
|
||||
);
|
||||
await this.smartdataDb.init();
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,10 @@ early.start('cloudly');
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { Cloudly } from './classes.cloudly.js';
|
||||
import { logger } from './cloudly.logging.js';
|
||||
import { logger } from './logger.js';
|
||||
const cloudlyQenv = new plugins.qenv.Qenv(paths.packageDir, paths.nogitDir, true);
|
||||
early.stop();
|
||||
|
||||
|
||||
/**
|
||||
* starts the cloudly instance
|
||||
*/
|
||||
@ -17,12 +16,14 @@ const runCli = async () => {
|
||||
|
||||
logger.log(
|
||||
'info',
|
||||
`running in environment ${await cloudlyQenv.getEnvVarOnDemand('SERVEZONE_ENVIRONMENT')}`
|
||||
`running in environment ${await cloudlyQenv.getEnvVarOnDemand('SERVEZONE_ENVIRONMENT')}`,
|
||||
);
|
||||
|
||||
await cloudlyInstance.start();
|
||||
const demoMod = await import('./demo/index.js');
|
||||
const demoMod = await import('./00demo/index.js');
|
||||
demoMod.installDemoData(cloudlyInstance);
|
||||
};
|
||||
|
||||
export { runCli, Cloudly };
|
||||
type ICloudlyConfig = plugins.servezoneInterfaces.data.ICloudlyConfig;
|
||||
export { type ICloudlyConfig };
|
||||
|
@ -9,8 +9,8 @@ export const logger = new plugins.smartlog.Smartlog({
|
||||
zone: null,
|
||||
companyunit: null,
|
||||
containerName: null,
|
||||
}
|
||||
},
|
||||
});
|
||||
logger.enableConsole({
|
||||
captureAll: false
|
||||
captureAll: false,
|
||||
});
|
@ -1,17 +1,137 @@
|
||||
import type { Cloudly } from '../classes.cloudly.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import type { Cloudly } from '../classes.cloudly.js';
|
||||
import { logger } from '../logger.js';
|
||||
import { Authorization } from './classes.authorization.js';
|
||||
import { User } from './classes.user.js';
|
||||
|
||||
export class AuthManager {
|
||||
cloudlyRef: Cloudly
|
||||
export interface IJwtData {
|
||||
userId: string;
|
||||
status: 'loggedIn' | 'loggedOut';
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
export class CloudlyAuthManager {
|
||||
cloudlyRef: Cloudly;
|
||||
public get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
public CUser = plugins.smartdata.setDefaultManagerForDoc(this, User);
|
||||
public CAuthorization = plugins.smartdata.setDefaultManagerForDoc(this, Authorization);
|
||||
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
public smartjwtInstance: plugins.smartjwt.SmartJwt<IJwtData>;
|
||||
|
||||
constructor(cloudlyRef: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRef;
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
}
|
||||
}
|
||||
|
||||
public async createNewSecureToken() {
|
||||
return plugins.smartunique.uniSimple('secureToken', 64);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
// lets setup the smartjwtInstance
|
||||
this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
|
||||
await this.smartjwtInstance.init();
|
||||
const kvStore = await this.cloudlyRef.config.appData.getKvStore();
|
||||
|
||||
const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = (await kvStore.readKey(
|
||||
'jwtKeypair',
|
||||
)) as plugins.tsclass.network.IJwtKeypair;
|
||||
|
||||
if (!existingJwtKeys) {
|
||||
await this.smartjwtInstance.createNewKeyPair();
|
||||
const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson();
|
||||
await kvStore.writeKey('jwtKeypair', newJwtKeys);
|
||||
} else {
|
||||
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
|
||||
}
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>(
|
||||
'adminLoginWithUsernameAndPassword',
|
||||
async (dataArg) => {
|
||||
let jwt: string;
|
||||
let expiresAtTimestamp: number = Date.now() + 3600 * 1000 * 24 * 7;
|
||||
const user = await User.findUserByUsernameAndPassword(dataArg.username, dataArg.password);
|
||||
if (!user) {
|
||||
logger.log('warn', 'login failed');
|
||||
throw new plugins.typedrequest.TypedResponseError('login failed');
|
||||
} else {
|
||||
jwt = await this.smartjwtInstance.createJWT({
|
||||
userId: user.id,
|
||||
status: 'loggedIn',
|
||||
expiresAt: expiresAtTimestamp,
|
||||
});
|
||||
logger.log('success', 'login successful');
|
||||
}
|
||||
return {
|
||||
identity: {
|
||||
jwt,
|
||||
userId: user.id,
|
||||
name: user.data.username,
|
||||
expiresAt: expiresAtTimestamp,
|
||||
role: user.data.role,
|
||||
type: user.data.type,
|
||||
},
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public async stop() {}
|
||||
|
||||
public validIdentityGuard = new plugins.smartguard.Guard<{
|
||||
identity: plugins.servezoneInterfaces.data.IIdentity;
|
||||
}>(
|
||||
async (dataArg) => {
|
||||
const jwt = dataArg.identity.jwt;
|
||||
const jwtData: IJwtData = await this.smartjwtInstance.verifyJWTAndGetData(jwt);
|
||||
const expired = jwtData.expiresAt < Date.now();
|
||||
plugins.smartexpect
|
||||
.expect(jwtData.status)
|
||||
.setFailMessage('user not logged in')
|
||||
.toEqual('loggedIn');
|
||||
plugins.smartexpect.expect(expired).setFailMessage(`jwt expired`).toBeFalse();
|
||||
plugins.smartexpect
|
||||
.expect(dataArg.identity.expiresAt)
|
||||
.setFailMessage(
|
||||
`expiresAt >>identity valid until:${dataArg.identity.expiresAt}, but jwt says: ${jwtData.expiresAt}<< has been tampered with`,
|
||||
)
|
||||
.toEqual(jwtData.expiresAt);
|
||||
plugins.smartexpect
|
||||
.expect(dataArg.identity.userId)
|
||||
.setFailMessage('userId has been tampered with')
|
||||
.toEqual(jwtData.userId);
|
||||
if (expired) {
|
||||
throw new Error('identity is expired');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
failedHint: 'identity is not valid.',
|
||||
name: 'validIdentityGuard',
|
||||
},
|
||||
);
|
||||
|
||||
public adminIdentityGuard = new plugins.smartguard.Guard<{
|
||||
identity: plugins.servezoneInterfaces.data.IIdentity;
|
||||
}>(
|
||||
async (dataArg) => {
|
||||
await plugins.smartguard.passGuardsOrReject(dataArg, [this.validIdentityGuard]);
|
||||
const jwt = dataArg.identity.jwt;
|
||||
const jwtData: IJwtData = await this.smartjwtInstance.verifyJWTAndGetData(jwt);
|
||||
const user = await this.CUser.getInstance({ id: jwtData.userId });
|
||||
const isAdminBool = user.data.role === 'admin';
|
||||
console.log(`user is admin: ${isAdminBool}`);
|
||||
return isAdminBool;
|
||||
},
|
||||
{
|
||||
failedHint: 'user is not admin.',
|
||||
name: 'adminIdentityGuard',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
@plugins.smartdata.managed()
|
||||
export class Authorization extends plugins.smartdata.SmartDataDbDoc<Authorization, Authorization> {
|
||||
|
||||
}
|
||||
export class Authorization extends plugins.smartdata.SmartDataDbDoc<Authorization, Authorization> {}
|
||||
|
@ -1,6 +1,52 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
@plugins.smartdata.managed()
|
||||
export class User extends plugins.smartdata.SmartDataDbDoc<User, User> {
|
||||
|
||||
}
|
||||
export class User extends plugins.smartdata.SmartDataDbDoc<
|
||||
User,
|
||||
plugins.servezoneInterfaces.data.IUser
|
||||
> {
|
||||
/**
|
||||
* creates a machine user
|
||||
*/
|
||||
public static async createMachineUser(userNameArg: string, roleArg: 'api' | 'cluster') {
|
||||
const user = new User();
|
||||
user.id = await User.getNewId();
|
||||
user.data = {
|
||||
type: 'machine',
|
||||
username: userNameArg,
|
||||
tokens: [
|
||||
{
|
||||
token: 'machineUser',
|
||||
expiresAt: Date.now() + 3600 * 1000 * 24 * 365,
|
||||
assignedRoles: ['admin'],
|
||||
},
|
||||
],
|
||||
role: 'api',
|
||||
};
|
||||
await user.save();
|
||||
return user;
|
||||
}
|
||||
|
||||
public static async findUserByUsernameAndPassword(usernameArg: string, passwordArg: string) {
|
||||
return await User.getInstance({
|
||||
data: {
|
||||
username: usernameArg,
|
||||
password: passwordArg,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
constructor(optionsArg?: plugins.servezoneInterfaces.data.IUser) {
|
||||
super();
|
||||
if (optionsArg) {
|
||||
Object.assign(this, optionsArg);
|
||||
}
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
@plugins.smartdata.unI()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: plugins.servezoneInterfaces.data.IUser['data'];
|
||||
}
|
||||
|
3
ts/manager.cert/cert.ts
Normal file
3
ts/manager.cert/cert.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export class Cert extends plugins.smartdata.SmartDataDbDoc<Cert, Cert> {}
|
14
ts/manager.cert/certmanager.ts
Normal file
14
ts/manager.cert/certmanager.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import type { Cloudly } from '../classes.cloudly.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export class CertManager {
|
||||
public cloudlyRef: Cloudly;
|
||||
|
||||
public get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
|
||||
constructor(cloudly: Cloudly) {
|
||||
this.cloudlyRef = cloudly;
|
||||
}
|
||||
}
|
@ -4,11 +4,12 @@ import * as plugins from '../plugins.js';
|
||||
* cluster defines a swarmkit cluster
|
||||
*/
|
||||
@plugins.smartdata.managed()
|
||||
export class Cluster extends plugins.smartdata.SmartDataDbDoc<Cluster, plugins.servezoneInterfaces.data.ICluster> {
|
||||
export class Cluster extends plugins.smartdata.SmartDataDbDoc<
|
||||
Cluster,
|
||||
plugins.servezoneInterfaces.data.ICluster
|
||||
> {
|
||||
// STATIC
|
||||
public static async fromConfigObject(
|
||||
configObjectArg: plugins.servezoneInterfaces.data.ICluster
|
||||
) {
|
||||
public static async fromConfigObject(configObjectArg: plugins.servezoneInterfaces.data.ICluster) {
|
||||
const newCluster = new Cluster();
|
||||
Object.assign(newCluster, configObjectArg);
|
||||
return newCluster;
|
@ -1,9 +1,10 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import { Cloudly } from '../classes.cloudly.js';
|
||||
import { logger } from '../cloudly.logging.js';
|
||||
import { logger } from '../logger.js';
|
||||
|
||||
import { Cluster } from './cluster.js';
|
||||
import { Cluster } from './classes.cluster.js';
|
||||
import { data } from '@serve.zone/interfaces';
|
||||
|
||||
export class ClusterManager {
|
||||
public ready = plugins.smartpromise.defer();
|
||||
@ -22,13 +23,12 @@ export class ClusterManager {
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_CreateCluster>(
|
||||
new plugins.typedrequest.TypedHandler('createCluster', async (dataArg) => {
|
||||
const cluster = await this.storeCluster({
|
||||
// TODO: guards
|
||||
const cluster = await this.createCluster({
|
||||
id: plugins.smartunique.uniSimple('cluster'),
|
||||
data: {
|
||||
userId: null, // this is created by the createCluster method
|
||||
name: dataArg.clusterName,
|
||||
jumpCode: plugins.smartunique.uniSimple('cluster'),
|
||||
jumpCodeUsedAt: null,
|
||||
secretKey: plugins.smartunique.shortId(16),
|
||||
acmeInfo: null,
|
||||
cloudlyUrl: `https://${this.cloudlyRef.config.data.publicUrl}:${this.cloudlyRef.config.data.publicPort}/`,
|
||||
servers: [],
|
||||
@ -38,21 +38,32 @@ export class ClusterManager {
|
||||
console.log(await cluster.createSavableObject());
|
||||
this.cloudlyRef.serverManager.ensureServerInfrastructure();
|
||||
return {
|
||||
clusterConfig: await cluster.createSavableObject(),
|
||||
cluster: await cluster.createSavableObject(),
|
||||
};
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_GetAllClusters>(
|
||||
new plugins.typedrequest.TypedHandler('getAllClusters', async (dataArg) => {
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IReq_Any_Cloudly_GetClusters>(
|
||||
new plugins.typedrequest.TypedHandler('getClusters', async (dataArg) => {
|
||||
// TODO: do authentication here
|
||||
const clusters = await this.getAllClusters();
|
||||
return {
|
||||
clusters: await Promise.all(
|
||||
clusters.map((clusterArg) => clusterArg.createSavableObject())
|
||||
clusters.map((clusterArg) => clusterArg.createSavableObject()),
|
||||
),
|
||||
};
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// delete cluster
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IReq_Any_Cloudly_DeleteClusterById>(
|
||||
new plugins.typedrequest.TypedHandler('deleteClusterById', async (reqDataArg, toolsArg) => {
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqDataArg);
|
||||
await this.deleteCluster(reqDataArg.clusterId);
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@ -70,25 +81,22 @@ export class ClusterManager {
|
||||
// TODO: implement getclusterConfigByServerIp
|
||||
}
|
||||
|
||||
public async getClusterConfigBy_JumpCode(jumpCodeArg: string) {
|
||||
public async getClusterBy_UserId(userIdArg: string) {
|
||||
await this.ready.promise;
|
||||
|
||||
return await Cluster.getInstance({
|
||||
data: {
|
||||
jumpCode: jumpCodeArg,
|
||||
userId: userIdArg,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async getClusterConfigBy_ClusterIdentifier(
|
||||
clusterIdentifier: plugins.servezoneInterfaces.data.IClusterIdentifier
|
||||
) {
|
||||
public async getClusterBy_Identity(clusterIdentity: plugins.servezoneInterfaces.data.IIdentity) {
|
||||
await this.ready.promise;
|
||||
|
||||
return await Cluster.getInstance({
|
||||
data: {
|
||||
name: clusterIdentifier.clusterName,
|
||||
secretKey: clusterIdentifier.secretKey,
|
||||
userId: clusterIdentity.userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -114,18 +122,39 @@ export class ClusterManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* allows storage of a config
|
||||
* creates a cluster (and a new user for it) and saves it
|
||||
* @param configName
|
||||
* @param configObjectArg
|
||||
*/
|
||||
public async storeCluster(configObjectArg: plugins.servezoneInterfaces.data.ICluster) {
|
||||
let clusterInstance = await Cluster.getInstance({ id: configObjectArg.id });
|
||||
if (!clusterInstance) {
|
||||
clusterInstance = await Cluster.fromConfigObject(configObjectArg);
|
||||
} else {
|
||||
Object.assign(clusterInstance, configObjectArg);
|
||||
}
|
||||
public async createCluster(configObjectArg: plugins.servezoneInterfaces.data.ICluster) {
|
||||
// TODO: guards
|
||||
// lets create the cluster user
|
||||
const clusterUser = new this.cloudlyRef.authManager.CUser({
|
||||
id: await this.cloudlyRef.authManager.CUser.getNewId(),
|
||||
data: {
|
||||
role: 'cluster',
|
||||
type: 'machine',
|
||||
tokens: [
|
||||
{
|
||||
expiresAt: Date.now() + 3600 * 1000 * 24 * 365,
|
||||
assignedRoles: ['cluster'],
|
||||
token: await this.cloudlyRef.authManager.createNewSecureToken(),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
await clusterUser.save();
|
||||
Object.assign(configObjectArg, {
|
||||
userId: clusterUser.id,
|
||||
});
|
||||
const clusterInstance = await Cluster.fromConfigObject(configObjectArg);
|
||||
await clusterInstance.save();
|
||||
return clusterInstance;
|
||||
}
|
||||
|
||||
public async deleteCluster(clusterId: string) {
|
||||
await this.ready.promise;
|
||||
const clusterInstance = await Cluster.getInstance({ id: clusterId });
|
||||
await clusterInstance.delete();
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { Cloudly } from '../classes.cloudly.js';
|
||||
import type { Cluster } from '../manager.cluster/classes.cluster.js';
|
||||
|
||||
/**
|
||||
* in charge of talking to coreflow services on clusters
|
||||
@ -13,24 +14,54 @@ export class CloudlyCoreflowManager {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
|
||||
|
||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByJumpCode>(
|
||||
new plugins.typedrequest.TypedHandler('getIdentityByJumpCode', async (requestData) => {
|
||||
const clusterConfig =
|
||||
await this.cloudlyRef.clusterManager.getClusterConfigBy_JumpCode(
|
||||
requestData.jumpCode
|
||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken>(
|
||||
new plugins.typedrequest.TypedHandler('getIdentityByToken', async (requestData) => {
|
||||
const user = await this.cloudlyRef.authManager.CUser.getInstance({
|
||||
data: {
|
||||
tokens: [
|
||||
{
|
||||
token: requestData.token,
|
||||
},
|
||||
], // find the proper user here.
|
||||
} as any,
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new plugins.typedrequest.TypedResponseError(
|
||||
'The supplied token is not valid. No matching user found.',
|
||||
);
|
||||
|
||||
if (!clusterConfig) {
|
||||
throw new plugins.typedrequest.TypedResponseError('The supplied jumpCode is not valid.');
|
||||
}
|
||||
|
||||
if (user.data.type !== 'machine') {
|
||||
throw new plugins.typedrequest.TypedResponseError(
|
||||
'The supplied token is not valid. The user is not a machine.',
|
||||
);
|
||||
}
|
||||
let cluster: Cluster;
|
||||
if (user.data.role === 'cluster') {
|
||||
cluster = await this.cloudlyRef.clusterManager.getClusterBy_UserId(user.id);
|
||||
}
|
||||
const expiryTimestamp = Date.now() + 3600 * 1000 * 24 * 365;
|
||||
return {
|
||||
clusterIdentifier: {
|
||||
clusterName: clusterConfig.data.name,
|
||||
secretKey: clusterConfig.data.secretKey,
|
||||
identity: {
|
||||
name: user.data.username,
|
||||
role: user.data.role,
|
||||
type: 'machine', // if someone authenticates by token, they are a machine, no matter what.
|
||||
userId: user.id,
|
||||
expiresAt: expiryTimestamp,
|
||||
...(cluster
|
||||
? {
|
||||
clusterId: cluster.id,
|
||||
clusterName: cluster.data.name,
|
||||
}
|
||||
: {}),
|
||||
jwt: await this.cloudlyRef.authManager.smartjwtInstance.createJWT({
|
||||
status: 'loggedIn',
|
||||
userId: user.id,
|
||||
expiresAt: expiryTimestamp,
|
||||
}),
|
||||
},
|
||||
};
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// lets enable the getting of cluster configs
|
||||
@ -38,36 +69,34 @@ export class CloudlyCoreflowManager {
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>(
|
||||
'getClusterConfig',
|
||||
async (dataArg) => {
|
||||
const clusterIdentifier = dataArg.clusterIdentifier;
|
||||
const identity = dataArg.identity;
|
||||
console.log('trying to get clusterConfigSet');
|
||||
console.log(dataArg);
|
||||
const clusterConfigSet =
|
||||
await this.cloudlyRef.clusterManager.getClusterConfigBy_ClusterIdentifier(
|
||||
clusterIdentifier
|
||||
);
|
||||
const cluster = await this.cloudlyRef.clusterManager.getClusterBy_Identity(identity);
|
||||
console.log('got cluster config and sending it back to coreflow');
|
||||
return {
|
||||
configData: await clusterConfigSet.createSavableObject()
|
||||
configData: await cluster.createSavableObject(),
|
||||
deploymentDirectives: [],
|
||||
};
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// lets enable getting of certificates
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetSslCertificate>(
|
||||
'getSslCertificate',
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain>(
|
||||
'getCertificateForDomain',
|
||||
async (dataArg) => {
|
||||
console.log(`got request for certificate ${dataArg.requiredCertName}`);
|
||||
console.log(`incoming API request for certificate ${dataArg.domainName}`);
|
||||
const cert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain(
|
||||
dataArg.requiredCertName
|
||||
dataArg.domainName,
|
||||
);
|
||||
console.log(`got certificate ready for reponse ${dataArg.requiredCertName}`);
|
||||
console.log(`got certificate ready for reponse ${dataArg.domainName}`);
|
||||
return {
|
||||
certificate: await cert.createSavableObject(),
|
||||
};
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
40
ts/manager.externalregistry/classes.externalregistry.ts
Normal file
40
ts/manager.externalregistry/classes.externalregistry.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import type { Cloudly } from 'ts/classes.cloudly.js';
|
||||
import type { ExternalRegistryManager } from './classes.externalregistrymanager.js';
|
||||
|
||||
export class ExternalRegistry extends plugins.smartdata.SmartDataDbDoc<ExternalRegistry, plugins.servezoneInterfaces.data.IExternalRegistry, ExternalRegistryManager> {
|
||||
// STATIC
|
||||
public static async getRegistryById(registryIdArg: string) {
|
||||
const externalRegistry = await this.getInstance({
|
||||
id: registryIdArg,
|
||||
});
|
||||
return externalRegistry;
|
||||
}
|
||||
|
||||
public static async getRegistries() {
|
||||
const externalRegistries = await this.getInstances({});
|
||||
return externalRegistries;
|
||||
}
|
||||
|
||||
public static async createExternalRegistry(registryDataArg: Partial<plugins.servezoneInterfaces.data.IExternalRegistry['data']>) {
|
||||
const externalRegistry = new ExternalRegistry();
|
||||
externalRegistry.id = await ExternalRegistry.getNewId();
|
||||
Object.assign(externalRegistry, registryDataArg);
|
||||
await externalRegistry.save();
|
||||
return externalRegistry;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: plugins.servezoneInterfaces.data.IExternalRegistry['data'];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { Cloudly } from '../classes.cloudly.js';
|
||||
import { ExternalRegistry } from './classes.externalregistry.js';
|
||||
|
||||
export class ExternalRegistryManager {
|
||||
public cloudlyRef: Cloudly;
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
public CExternalRegistry = plugins.smartdata.setDefaultManagerForDoc(this, ExternalRegistry);
|
||||
|
||||
get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
|
||||
constructor(cloudlyRef: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRef;
|
||||
}
|
||||
|
||||
public async start() {
|
||||
// lets set up a typedrouter
|
||||
this.typedrouter.addTypedRouter(this.typedrouter);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.externalRegistry.IReq_GetRegistryById>(
|
||||
new plugins.typedrequest.TypedHandler('getExternalRegistryById', async (dataArg) => {
|
||||
const registry = await ExternalRegistry.getRegistryById(dataArg.id);
|
||||
return {
|
||||
registry: await registry.createSavableObject(),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.externalRegistry.IReq_GetRegistries>(
|
||||
new plugins.typedrequest.TypedHandler('getExternalRegistries', async (dataArg) => {
|
||||
const registries = await ExternalRegistry.getRegistries();
|
||||
return {
|
||||
registries: await Promise.all(
|
||||
registries.map((registry) => registry.createSavableObject())
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.externalRegistry.IReq_CreateRegistry>(
|
||||
new plugins.typedrequest.TypedHandler('createExternalRegistry', async (dataArg) => {
|
||||
const registry = await ExternalRegistry.createExternalRegistry(dataArg.registryData);
|
||||
return {
|
||||
registry: await registry.createSavableObject(),
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
2
ts/manager.externalregistry/index.ts
Normal file
2
ts/manager.externalregistry/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './classes.externalregistrymanager.js';
|
||||
export * from './classes.externalregistry.js';
|
@ -1,12 +1,26 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import type { ImageManager } from './classes.imagemanager.js';
|
||||
|
||||
@plugins.smartdata.Manager()
|
||||
export class Image extends plugins.smartdata.SmartDataDbDoc<Image, plugins.servezoneInterfaces.data.IImage, ImageManager> {
|
||||
public static async create(imageDataArg: Partial<plugins.servezoneInterfaces.data.IImage['data']>) {
|
||||
@plugins.smartdata.managed()
|
||||
export class Image extends plugins.smartdata.SmartDataDbDoc<
|
||||
Image,
|
||||
plugins.servezoneInterfaces.data.IImage,
|
||||
ImageManager
|
||||
> {
|
||||
public static async create(
|
||||
imageDataArg: Partial<plugins.servezoneInterfaces.data.IImage['data']>,
|
||||
) {
|
||||
const image = new Image();
|
||||
image.id = plugins.smartunique.uni('image');
|
||||
Object.assign(image.data, imageDataArg);
|
||||
image.id = await this.getNewId();
|
||||
console.log(imageDataArg);
|
||||
Object.assign(image, {
|
||||
data: {
|
||||
name: imageDataArg.name,
|
||||
description: imageDataArg.description,
|
||||
versions: [],
|
||||
},
|
||||
});
|
||||
console.log((Image as any).saveableProperties);
|
||||
await image.save();
|
||||
return image;
|
||||
}
|
||||
@ -18,4 +32,16 @@ export class Image extends plugins.smartdata.SmartDataDbDoc<Image, plugins.serve
|
||||
public data: plugins.servezoneInterfaces.data.IImage['data'];
|
||||
|
||||
public async getVersions() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a storage path
|
||||
* note: this is relative to the storage method defined by the imageManager
|
||||
*/
|
||||
public async getStoragePath(versionStringArg: string) {
|
||||
return `${this.data.name}:${versionStringArg}`.replace('/', '__');
|
||||
}
|
||||
|
||||
public async getWriteStream() {}
|
||||
|
||||
public async getReadStream() {}
|
||||
}
|
||||
|
@ -1,96 +1,173 @@
|
||||
import type { Cloudly } from '../classes.cloudly.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
|
||||
import { Image } from './classes.image.js';
|
||||
|
||||
export class ImageManager {
|
||||
cloudlyRef: Cloudly;
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
public smartbucketInstance: plugins.smartbucket.SmartBucket;
|
||||
public imageDir: plugins.smartbucket.Directory;
|
||||
public dockerImageStore: plugins.docker.DockerImageStore;
|
||||
|
||||
get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
public CImage = plugins.smartdata.setDefaultManagerForDoc(this, Image);
|
||||
|
||||
smartbucketInstance: plugins.smartbucket.SmartBucket;
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
|
||||
'createImage',
|
||||
async (reqArg, toolsArg) => {
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
|
||||
const image = await this.CImage.create({
|
||||
name: reqArg.name,
|
||||
description: reqArg.description,
|
||||
versions: [],
|
||||
});
|
||||
return {
|
||||
image: await image.createSavableObject(),
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetImage>(
|
||||
new plugins.typedrequest.TypedHandler('getImage', async (reqArg, toolsArg) => {
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
|
||||
const image = await this.CImage.getInstance({
|
||||
id: reqArg.imageId,
|
||||
});
|
||||
return {
|
||||
image: await image.createSavableObject(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_DeleteImage>(
|
||||
'deleteImage',
|
||||
async (reqArg, toolsArg) => {
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
|
||||
const image = await this.CImage.getInstance({
|
||||
id: reqArg.imageId,
|
||||
});
|
||||
await image.delete();
|
||||
return {};
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
|
||||
'getAllImages',
|
||||
async (requestArg) => {
|
||||
async (requestArg, toolsArg) => {
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], requestArg);
|
||||
const images = await this.CImage.getInstances({});
|
||||
return {
|
||||
images: await Promise.all(
|
||||
images.map((image) => {
|
||||
return image.createSavableObject();
|
||||
})
|
||||
}),
|
||||
),
|
||||
};
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
|
||||
'createImage',
|
||||
async (reqArg) => {
|
||||
const image = await this.CImage.create({
|
||||
name: reqArg.name,
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PushImageVersion>(
|
||||
'pushImageVersion',
|
||||
async (reqArg, toolsArg) => {
|
||||
await plugins.smartguard.passGuardsOrReject(reqArg, [
|
||||
this.cloudlyRef.authManager.validIdentityGuard,
|
||||
]);
|
||||
const refImage = await this.CImage.getInstance({
|
||||
id: reqArg.imageId,
|
||||
});
|
||||
if (!refImage) {
|
||||
throw new plugins.typedrequest.TypedResponseError('Image not found');
|
||||
}
|
||||
const imageVersion = reqArg.versionString;
|
||||
console.log(
|
||||
`got request to push image version ${imageVersion} for image ${refImage.data.name}`,
|
||||
);
|
||||
const imagePushStream = reqArg.imageStream;
|
||||
(async () => {
|
||||
const smartWebDuplex = new plugins.smartstream.webstream.WebDuplexStream<
|
||||
Uint8Array,
|
||||
Uint8Array
|
||||
>({
|
||||
writeFunction: async (chunkArg, toolsArg) => {
|
||||
console.log(chunkArg);
|
||||
return chunkArg;
|
||||
},
|
||||
});
|
||||
imagePushStream.writeToWebstream(smartWebDuplex.writable);
|
||||
await this.dockerImageStore.storeImage(
|
||||
refImage.id,
|
||||
plugins.smartstream.SmartDuplex.fromWebReadableStream(smartWebDuplex.readable),
|
||||
);
|
||||
})();
|
||||
return {
|
||||
image: await image.createSavableObject(),
|
||||
allowed: true,
|
||||
};
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PushImage>(
|
||||
'pushImage',
|
||||
async (reqArg) => {
|
||||
const pushStream = reqArg.imageStream;
|
||||
return {};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PullImage>(
|
||||
'pullImage',
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PullImageVersion>(
|
||||
'pullImageVersion',
|
||||
async (reqArg) => {
|
||||
const image = await this.CImage.getInstance({
|
||||
data: {
|
||||
name: reqArg.name,
|
||||
},
|
||||
id: reqArg.imageId,
|
||||
});
|
||||
const imageVersion = null;
|
||||
const imageVersion = image.data.versions.find(
|
||||
(version) => version.versionString === reqArg.versionString,
|
||||
);
|
||||
const readable = this.imageDir.fastGetStream(
|
||||
{
|
||||
path: await image.getStoragePath(reqArg.versionString),
|
||||
},
|
||||
'webstream',
|
||||
);
|
||||
const imageVirtualStream = new plugins.typedrequest.VirtualStream();
|
||||
return {
|
||||
imageStream: imageVirtualStream,
|
||||
};
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
// lets setup s3
|
||||
const s3Descriptor: plugins.tsclass.storage.IS3Descriptor =
|
||||
await this.cloudlyRef.config.appData.waitForAndGetKey('s3Descriptor');
|
||||
console.log(this.cloudlyRef.config.data.s3Descriptor);
|
||||
this.smartbucketInstance = new plugins.smartbucket.SmartBucket(
|
||||
this.cloudlyRef.config.data.s3Descriptor
|
||||
this.cloudlyRef.config.data.s3Descriptor,
|
||||
);
|
||||
const bucket = await this.smartbucketInstance.getBucketByName('cloudly-test');
|
||||
await bucket.fastPut({ path: 'test/test.txt', contents: 'hello' });
|
||||
}
|
||||
await bucket.fastPut({ path: 'images/00init', contents: 'init' });
|
||||
|
||||
public async createImage(nameArg: string) {
|
||||
const newImage = await this.CImage.create({
|
||||
name: nameArg,
|
||||
this.imageDir = await bucket.getDirectoryFromPath({
|
||||
path: '/images',
|
||||
});
|
||||
|
||||
// lets setup dockerstore
|
||||
await plugins.smartfile.fs.ensureDir(paths.dockerImageStoreDir);
|
||||
this.dockerImageStore = new plugins.docker.DockerImageStore({
|
||||
localDirPath: paths.dockerImageStoreDir,
|
||||
bucketDir: this.imageDir,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,4 +12,4 @@ export class LogManager {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ export class SecretBundle extends plugins.smartdata.SmartDataDbDoc<
|
||||
secretGroups.push(
|
||||
await SecretGroup.getInstance({
|
||||
id: secretGroupId,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
return secretGroups;
|
||||
@ -59,4 +59,16 @@ export class SecretBundle extends plugins.smartdata.SmartDataDbDoc<
|
||||
}
|
||||
return returnObject;
|
||||
}
|
||||
|
||||
public async getFlatKeyValueObject(environmentArg: string) {
|
||||
if (!environmentArg) {
|
||||
throw new Error('environment is required');
|
||||
}
|
||||
const secretGroups = await this.getSecretGroups();
|
||||
const returnObject = {};
|
||||
for (const secretGroup of secretGroups) {
|
||||
returnObject[secretGroup.data.key] = secretGroup.data.environments[environmentArg].value;
|
||||
}
|
||||
return returnObject;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import { SecretBundle } from './classes.secretbundle.js';
|
||||
import { SecretGroup } from './classes.secretgroup.js';
|
||||
import { logger } from '../cloudly.logging.js';
|
||||
import { logger } from '../logger.js';
|
||||
import type { Cloudly } from '../classes.cloudly.js';
|
||||
|
||||
/**
|
||||
@ -34,121 +34,143 @@ export class CloudlySecretManager {
|
||||
// lets set up a typedrouter
|
||||
this.typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>(
|
||||
'adminLoginWithUsernameAndPassword',
|
||||
async (dataArg) => {
|
||||
let jwt: string;
|
||||
// console.log(dataArg);
|
||||
if (dataArg.username !== 'admin' || dataArg.password !== 'password') {
|
||||
logger.log('warn', 'login failed');
|
||||
} else {
|
||||
jwt = await this.cloudlyRef.config.smartjwtInstance.createJWT({
|
||||
status: 'loggedIn',
|
||||
});
|
||||
logger.log('success', 'login successful');
|
||||
}
|
||||
return {
|
||||
jwt,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
|
||||
'adminGetConfigBundlesAndSecretGroups',
|
||||
async (dataArg) => {
|
||||
dataArg.jwt
|
||||
// secretbundle routes
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetSecretBundles>(
|
||||
new plugins.typedrequest.TypedHandler(
|
||||
'getSecretBundles',
|
||||
async (dataArg, toolsArg) => {
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], dataArg);
|
||||
dataArg.identity.jwt;
|
||||
const secretBundles = await SecretBundle.getInstances({});
|
||||
const secretGroups = await SecretGroup.getInstances({});
|
||||
return {
|
||||
secretBundles: [
|
||||
...(await Promise.all(
|
||||
secretBundles.map((configBundle) => configBundle.createSavableObject())
|
||||
secretBundles.map((configBundle) => configBundle.createSavableObject()),
|
||||
)),
|
||||
],
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_CreateSecretBundle>(
|
||||
new plugins.typedrequest.TypedHandler('createSecretBundle', async (dataArg) => {
|
||||
const secretBundle = new SecretBundle();
|
||||
secretBundle.id = plugins.smartunique.shortId(8);
|
||||
secretBundle.data = dataArg.secretBundle.data;
|
||||
await secretBundle.save();
|
||||
return {
|
||||
resultSecretBundle: await secretBundle.createSavableObject(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_UpdateSecretBundle>(
|
||||
new plugins.typedrequest.TypedHandler('updateSecretBundle', async (dataArg) => {
|
||||
const secretBundle = await SecretBundle.getInstance({
|
||||
id: dataArg.secretBundle.id,
|
||||
});
|
||||
secretBundle.data = dataArg.secretBundle.data;
|
||||
await secretBundle.save();
|
||||
return {
|
||||
resultSecretBundle: await secretBundle.createSavableObject(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_DeleteSecretBundleById>(
|
||||
new plugins.typedrequest.TypedHandler('deleteSecretBundleById', async (dataArg) => {
|
||||
const secretBundle = await SecretBundle.getInstance({
|
||||
id: dataArg.secretBundleId,
|
||||
});
|
||||
await secretBundle.delete();
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
// secretgroup routes
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretgroup.IReq_GetSecretGroups>(
|
||||
new plugins.typedrequest.TypedHandler(
|
||||
'getSecretGroups',
|
||||
async (dataArg, toolsArg) => {
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], dataArg);
|
||||
dataArg.identity.jwt;
|
||||
const secretGroups = await SecretGroup.getInstances({});
|
||||
return {
|
||||
secretGroups: [
|
||||
...(await Promise.all(
|
||||
secretGroups.map((secretGroup) => secretGroup.createSavableObject())
|
||||
secretGroups.map((secretGroup) => secretGroup.createSavableObject()),
|
||||
)),
|
||||
],
|
||||
};
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_CreateConfigBundlesAndSecretGroups>(
|
||||
new plugins.typedrequest.TypedHandler(
|
||||
'adminCreateConfigBundlesAndSecretGroups',
|
||||
async (dataArg) => {
|
||||
for (const secretGroupObject of dataArg.secretGroups) {
|
||||
const secretGroup = new SecretGroup();
|
||||
secretGroup.id = plugins.smartunique.shortId(8);
|
||||
secretGroup.data = secretGroupObject.data;
|
||||
await secretGroup.save();
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
}
|
||||
)
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretgroup.IReq_CreateSecretGroup>(
|
||||
new plugins.typedrequest.TypedHandler('createSecretGroup', async (dataArg) => {
|
||||
const secretGroup = new SecretGroup();
|
||||
secretGroup.id = plugins.smartunique.shortId(8);
|
||||
secretGroup.data = dataArg.secretGroup.data;
|
||||
await secretGroup.save();
|
||||
return {
|
||||
resultSecretGroup: await secretGroup.createSavableObject(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretgroup.IReq_UpdateSecretGroup>(
|
||||
new plugins.typedrequest.TypedHandler('updateSecretGroup', async (dataArg) => {
|
||||
const secretGroup = await SecretGroup.getInstance({
|
||||
id: dataArg.secretGroup.id,
|
||||
});
|
||||
secretGroup.data = dataArg.secretGroup.data;
|
||||
await secretGroup.save();
|
||||
return {
|
||||
resultSecretGroup: await secretGroup.createSavableObject(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretgroup.IReq_DeleteSecretGroupById>(
|
||||
new plugins.typedrequest.TypedHandler('deleteSecretGroupById', async (dataArg) => {
|
||||
const secretGroup = await SecretGroup.getInstance({
|
||||
id: dataArg.secretGroupId,
|
||||
});
|
||||
await secretGroup.delete();
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_DeleteConfigBundlesAndSecretGroups>(
|
||||
'adminDeleteConfigBundlesAndSecretGroups',
|
||||
async (dataArg) => {
|
||||
for (const secretGroupId of dataArg.secretGroupIds) {
|
||||
const secretGroup = await SecretGroup.getInstance({
|
||||
id: secretGroupId,
|
||||
});
|
||||
await secretGroup.delete();
|
||||
}
|
||||
for (const secretBundleId of dataArg.secretBundleIds) {
|
||||
const configBundle = await SecretBundle.getInstance({
|
||||
id: secretBundleId,
|
||||
});
|
||||
await configBundle.delete();
|
||||
console.log(`deleted configbundle ${secretBundleId}`);
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// lets add typedrouter routes for accessing the configvailt from apps
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_GetEnvBundle>(
|
||||
'getEnvBundle',
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetFlatKeyValueObject>(
|
||||
'getFlatKeyValueObject',
|
||||
async (dataArg) => {
|
||||
const wantedBundle = await SecretBundle.getInstance({
|
||||
data: {
|
||||
authorizations: {
|
||||
// @ts-ignore
|
||||
$elemMatch: {
|
||||
secretAccessKey: dataArg.authorization,
|
||||
secretAccessKey: dataArg.secretBundleAuthorization.secretAccessKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const authorization = await wantedBundle.getAuthorizationFromAuthKey(
|
||||
dataArg.authorization
|
||||
dataArg.secretBundleAuthorization.secretAccessKey,
|
||||
);
|
||||
return {
|
||||
envBundle: {
|
||||
configKeyValueObject: await wantedBundle.getKeyValueObjectForEnvironment(
|
||||
authorization.environment
|
||||
),
|
||||
environment: authorization.environment,
|
||||
timeSensitive: false,
|
||||
},
|
||||
flatKeyValueObject: await wantedBundle.getKeyValueObjectForEnvironment(
|
||||
authorization.environment,
|
||||
),
|
||||
};
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
90
ts/manager.server/classes.curlfresh.ts
Normal file
90
ts/manager.server/classes.curlfresh.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { logger } from '../logger.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
import type { CloudlyServerManager } from './classes.servermanager.js';
|
||||
|
||||
export class CurlFresh {
|
||||
public optionsArg = {
|
||||
npmRegistry: 'https://registry.npmjs.org',
|
||||
};
|
||||
public scripts = {
|
||||
'setup.sh': `#!/bin/bash
|
||||
|
||||
# lets update the system and install curl
|
||||
# might be installed already, but entrypoint could have been wget
|
||||
apt-get update
|
||||
apt-get install -y --force-yes curl
|
||||
|
||||
# Basic updating of the software lists
|
||||
echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||
apt-get update
|
||||
apt-get upgrade -y --force-yes
|
||||
apt-get install -y --force-yes fail2ban curl git
|
||||
curl -sL https://deb.nodesource.com/setup_18.x | bash
|
||||
|
||||
# Install docker
|
||||
curl -sSL https://get.docker.com/ | sh
|
||||
|
||||
# Install default nodejs to run nodejs tools
|
||||
apt-get install -y nodejs zsh
|
||||
zsh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
|
||||
npm config set unsafe-perm true
|
||||
|
||||
# lets install pnpm
|
||||
curl -fsSL https://get.pnpm.io/install.sh | sh -
|
||||
|
||||
# lets make sure we use the correct npm registry
|
||||
bash -c "npm config set registry ${this.optionsArg.npmRegistry}"
|
||||
|
||||
# lets install spark
|
||||
bash -c "pnpm install -g @serve.zone/spark"
|
||||
|
||||
# lets install the spark daemon
|
||||
bash -c "spark installdaemon"
|
||||
|
||||
# TODO: start spark with jump code
|
||||
`,
|
||||
};
|
||||
|
||||
public serverManagerRef: CloudlyServerManager;
|
||||
public curlFreshRoute: plugins.typedserver.servertools.Route;
|
||||
public handler = new plugins.typedserver.servertools.Handler('ALL', async (req, res) => {
|
||||
logger.log('info', 'curlfresh handler called. a server might be coming online soon :)');
|
||||
const scriptname = req.params.scriptname;
|
||||
switch (scriptname) {
|
||||
case 'setup.sh':
|
||||
logger.log('info', 'sending setup.sh');
|
||||
res.type('application/x-sh');
|
||||
res.send(this.scripts['setup.sh']);
|
||||
break;
|
||||
default:
|
||||
res.send('no script found');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
constructor(serverManagerRefArg: CloudlyServerManager) {
|
||||
this.serverManagerRef = serverManagerRefArg;
|
||||
}
|
||||
public async getServerUserData(): Promise<string> {
|
||||
const sslMode =
|
||||
await this.serverManagerRef.cloudlyRef.config.appData.waitForAndGetKey('sslMode');
|
||||
let protocol: 'http' | 'https';
|
||||
if (sslMode === 'none') {
|
||||
protocol = 'http';
|
||||
} else {
|
||||
protocol = 'https';
|
||||
}
|
||||
|
||||
const domain =
|
||||
await this.serverManagerRef.cloudlyRef.config.appData.waitForAndGetKey('publicUrl');
|
||||
const port =
|
||||
await this.serverManagerRef.cloudlyRef.config.appData.waitForAndGetKey('publicPort');
|
||||
|
||||
const serverUserData = `#cloud-config
|
||||
runcmd:
|
||||
- curl -o- ${protocol}://${domain}:${port}/curlfresh/setup.sh | sh
|
||||
`;
|
||||
console.log(serverUserData);
|
||||
return serverUserData;
|
||||
}
|
||||
}
|
@ -4,10 +4,13 @@ import * as plugins from '../plugins.js';
|
||||
* cluster defines a swarmkit cluster
|
||||
*/
|
||||
@plugins.smartdata.Manager()
|
||||
export class Server extends plugins.smartdata.SmartDataDbDoc<Server, plugins.servezoneInterfaces.data.IServer> {
|
||||
export class Server extends plugins.smartdata.SmartDataDbDoc<
|
||||
Server,
|
||||
plugins.servezoneInterfaces.data.IServer
|
||||
> {
|
||||
// STATIC
|
||||
public static async createFromHetznerServer(
|
||||
hetznerServerArg: plugins.hetznercloud.HetznerServer
|
||||
hetznerServerArg: plugins.hetznercloud.HetznerServer,
|
||||
) {
|
||||
const newServer = new Server();
|
||||
newServer.id = plugins.smartunique.shortId(8);
|
||||
@ -16,7 +19,7 @@ export class Server extends plugins.smartdata.SmartDataDbDoc<Server, plugins.ser
|
||||
requiredDebianPackages: [],
|
||||
sshKeys: [],
|
||||
type: 'hetzner',
|
||||
}
|
||||
};
|
||||
Object.assign(newServer, { data });
|
||||
await newServer.save();
|
||||
return newServer;
|
@ -1,11 +1,13 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { Cloudly } from '../classes.cloudly.js';
|
||||
import { Cluster } from '../manager.cluster/cluster.js';
|
||||
import { Server } from './server.js';
|
||||
import { Cluster } from '../manager.cluster/classes.cluster.js';
|
||||
import { Server } from './classes.server.js';
|
||||
import { CurlFresh } from './classes.curlfresh.js';
|
||||
|
||||
export class CloudlyServerManager {
|
||||
public cloudlyRef: Cloudly;
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
public curlfreshInstance = new CurlFresh(this);
|
||||
|
||||
public hetznerAccount: plugins.hetznercloud.HetznerAccount;
|
||||
|
||||
@ -27,17 +29,19 @@ export class CloudlyServerManager {
|
||||
const serverId = requestData.serverId;
|
||||
const server = await this.CServer.getInstance({
|
||||
id: serverId,
|
||||
})
|
||||
});
|
||||
return {
|
||||
configData: await server.createSavableObject(),
|
||||
};
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
this.hetznerAccount = new plugins.hetznercloud.HetznerAccount(this.cloudlyRef.config.data.hetznerToken);
|
||||
this.hetznerAccount = new plugins.hetznercloud.HetznerAccount(
|
||||
this.cloudlyRef.config.data.hetznerToken,
|
||||
);
|
||||
}
|
||||
|
||||
public async stop() {}
|
||||
@ -63,16 +67,19 @@ export class CloudlyServerManager {
|
||||
labels: {
|
||||
clusterId: cluster.id,
|
||||
priority: '1',
|
||||
}
|
||||
},
|
||||
userData: await this.curlfreshInstance.getServerUserData(),
|
||||
});
|
||||
const newServer = await Server.createFromHetznerServer(server);
|
||||
console.log(`cluster created new server for cluster ${cluster.id}`);
|
||||
} else {
|
||||
console.log(`cluster ${cluster.id} already has servers. Making sure that they actually exist in the real world...`);
|
||||
console.log(
|
||||
`cluster ${cluster.id} already has servers. Making sure that they actually exist in the real world...`,
|
||||
);
|
||||
// if there is a server, make sure that it exists
|
||||
for (const server of servers) {
|
||||
const hetznerServer = await this.hetznerAccount.getServersByLabel({
|
||||
'clusterId': cluster.id
|
||||
clusterId: cluster.id,
|
||||
});
|
||||
if (!hetznerServer) {
|
||||
console.log(`server ${server.id} does not exist in the real world. Creating it now...`);
|
||||
@ -83,7 +90,7 @@ export class CloudlyServerManager {
|
||||
labels: {
|
||||
clusterId: cluster.id,
|
||||
priority: '1',
|
||||
}
|
||||
},
|
||||
});
|
||||
const newServer = await Server.createFromHetznerServer(hetznerServer);
|
||||
}
|
||||
@ -96,7 +103,7 @@ export class CloudlyServerManager {
|
||||
const results = await this.CServer.getInstances({
|
||||
data: {
|
||||
assignedClusterId: clusterArg.id,
|
||||
}
|
||||
},
|
||||
});
|
||||
return results;
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
import { SecretBundle } from 'ts/manager.secret/classes.secretbundle.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
import { ServiceManager } from './classes.servicemanager.js';
|
||||
|
||||
export class Service extends plugins.smartdata.SmartDataDbDoc<
|
||||
Service,
|
||||
plugins.servezoneInterfaces.data.IService,
|
||||
ServiceManager
|
||||
> {
|
||||
// STATIC
|
||||
public static async getServiceById(serviceIdArg: string) {
|
||||
const service = await this.getInstance({
|
||||
id: serviceIdArg,
|
||||
});
|
||||
return service;
|
||||
}
|
||||
|
||||
public static async getServices() {
|
||||
const services = await this.getInstances({});
|
||||
return services;
|
||||
}
|
||||
|
||||
public static async createService(serviceDataArg: Partial<plugins.servezoneInterfaces.data.IService['data']>) {
|
||||
const service = new Service();
|
||||
service.id = await Service.getNewId();
|
||||
Object.assign(service, serviceDataArg);
|
||||
await service.save();
|
||||
return service;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
@plugins.smartdata.svDb()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: plugins.servezoneInterfaces.data.IService['data'];
|
||||
|
||||
/**
|
||||
* a service runs in a specific environment
|
||||
* so -> this method returns the secret bundles as a flat object accordingly.
|
||||
* in other words, it resolves secret groups for the relevant environment
|
||||
* @param environmentArg
|
||||
*/
|
||||
public async getSecretBundlesAsFlatObject(environmentArg: string = 'production') {
|
||||
const secreBundleIds = this.data.additionalSecretBundleIds || [];
|
||||
secreBundleIds.push(this.data.secretBundleId); // put this last, so it overwrites any other secret bundles.
|
||||
let finalFlatObject = {};
|
||||
for (const secretBundleId of secreBundleIds) {
|
||||
const secretBundle = await SecretBundle.getInstance({
|
||||
id: secretBundleId,
|
||||
});
|
||||
const flatObject = await secretBundle.getFlatKeyValueObject(environmentArg);
|
||||
Object.assign(finalFlatObject, flatObject);
|
||||
}
|
||||
return finalFlatObject;
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
import type { Cloudly } from '../classes.cloudly.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
import { Service } from './classes.service.js';
|
||||
|
||||
export class ServiceManager {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
public cloudlyRef: Cloudly;
|
||||
|
||||
get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
|
||||
public CService = plugins.smartdata.setDefaultManagerForDoc(this, Service);
|
||||
|
||||
constructor(cloudlyRef: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRef;
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_GetServices>(
|
||||
'getServices',
|
||||
async (reqArg) => {
|
||||
await plugins.smartguard.passGuardsOrReject(reqArg, [
|
||||
this.cloudlyRef.authManager.validIdentityGuard,
|
||||
]);
|
||||
|
||||
const services = await this.CService.getInstances({});
|
||||
|
||||
return {
|
||||
services: await Promise.all(
|
||||
services.map((service) => {
|
||||
return service.createSavableObject();
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_GetServiceSecretBundlesAsFlatObject>(
|
||||
'getServiceSecretBundlesAsFlatObject',
|
||||
async (dataArg) => {
|
||||
const service = await Service.getInstance({
|
||||
id: dataArg.serviceId,
|
||||
});
|
||||
const flatKeyValueObject = await service.getSecretBundlesAsFlatObject(dataArg.environment);
|
||||
return {
|
||||
flatKeyValueObject: flatKeyValueObject,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_CreateService>(
|
||||
'createService',
|
||||
async (dataArg) => {
|
||||
const service = await Service.createService(dataArg.serviceData);
|
||||
return {
|
||||
service: await service.createSavableObject(),
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_UpdateService>(
|
||||
'updateService',
|
||||
async (dataArg) => {
|
||||
const service = await Service.getInstance({
|
||||
id: dataArg.serviceId,
|
||||
});
|
||||
service.data = {
|
||||
...service.data,
|
||||
...dataArg.serviceData,
|
||||
};
|
||||
await service.save();
|
||||
return {
|
||||
service: await service.createSavableObject(),
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_DeleteServiceById>(
|
||||
'deleteServiceById',
|
||||
async (dataArg) => {
|
||||
const service = await Service.getInstance({
|
||||
id: dataArg.serviceId,
|
||||
});
|
||||
await service.delete();
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ export class ExternalApiManager {
|
||||
return {
|
||||
networkNodes,
|
||||
};
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { Cloudly } from '../classes.cloudly.js';
|
||||
|
||||
import { logger } from '../cloudly.logging.js';
|
||||
import { logger } from '../logger.js';
|
||||
|
||||
export class CloudlyTaskmanager {
|
||||
public cloudlyRef: Cloudly;
|
||||
|
@ -1,5 +1,9 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '../');
|
||||
export const packageDir = plugins.path.join(
|
||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||
'../',
|
||||
);
|
||||
export const nogitDir = plugins.path.join(packageDir, '.nogit/');
|
||||
export const dockerImageStoreDir = plugins.path.join(nogitDir, './dockerimagestore/');
|
||||
export const distServeDir = plugins.path.join(packageDir, './dist_serve');
|
||||
|
@ -9,13 +9,13 @@ import * as typedsocket from '@api.global/typedsocket';
|
||||
|
||||
export { typedrequest, typedsocket };
|
||||
|
||||
// @mojoio scope
|
||||
// @apiclient.xyz scope
|
||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||
import * as digitalocean from '@apiclient.xyz/digitalocean';
|
||||
import * as docker from '@apiclient.xyz/docker';
|
||||
import * as hetznercloud from '@apiclient.xyz/hetznercloud';
|
||||
import * as slack from '@apiclient.xyz/slack';
|
||||
|
||||
export { cloudflare, digitalocean, hetznercloud, slack };
|
||||
export { cloudflare, docker, hetznercloud, slack };
|
||||
|
||||
// @tsclass scope
|
||||
import * as tsclass from '@tsclass/tsclass';
|
||||
@ -29,10 +29,11 @@ import * as qenv from '@push.rocks/qenv';
|
||||
import * as smartacme from '@push.rocks/smartacme';
|
||||
import * as smartbucket from '@push.rocks/smartbucket';
|
||||
import * as smartcli from '@push.rocks/smartcli';
|
||||
import * as smartclickhouse from '@push.rocks/smartclickhouse';
|
||||
import * as smartdata from '@push.rocks/smartdata';
|
||||
import * as smartdelay from '@push.rocks/smartdelay';
|
||||
import * as smartexit from '@push.rocks/smartexit';
|
||||
import * as typedserver from '@api.global/typedserver';
|
||||
import * as smartexpect from '@push.rocks/smartexpect';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartguard from '@push.rocks/smartguard';
|
||||
import * as smartjson from '@push.rocks/smartjson';
|
||||
@ -42,9 +43,11 @@ import * as smartpath from '@push.rocks/smartpath';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartrequest from '@push.rocks/smartrequest';
|
||||
import * as smartssh from '@push.rocks/smartssh';
|
||||
import * as smartstream from '@push.rocks/smartstream';
|
||||
import * as smartstring from '@push.rocks/smartstring';
|
||||
import * as smartunique from '@push.rocks/smartunique';
|
||||
import * as taskbuffer from '@push.rocks/taskbuffer';
|
||||
import * as typedserver from '@api.global/typedserver';
|
||||
|
||||
export {
|
||||
npmextra,
|
||||
@ -53,9 +56,10 @@ export {
|
||||
smartacme,
|
||||
smartbucket,
|
||||
smartcli,
|
||||
smartclickhouse,
|
||||
smartdata,
|
||||
smartexit,
|
||||
typedserver,
|
||||
smartexpect,
|
||||
smartdelay,
|
||||
smartfile,
|
||||
smartguard,
|
||||
@ -66,9 +70,11 @@ export {
|
||||
smartpromise,
|
||||
smartrequest,
|
||||
smartssh,
|
||||
smartstream,
|
||||
smartstring,
|
||||
smartunique,
|
||||
taskbuffer,
|
||||
typedserver,
|
||||
};
|
||||
|
||||
// @servezone scope
|
||||
|
240
ts_apiclient/classes.cloudlyapiclient.ts
Normal file
240
ts_apiclient/classes.cloudlyapiclient.ts
Normal file
@ -0,0 +1,240 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export type TClientType = 'api' | 'ci' | 'coreflow' | 'cli' | 'serverconfig';
|
||||
|
||||
import { Image } from './classes.image.js';
|
||||
import { Service } from './classes.service.js';
|
||||
import { Cluster } from './classes.cluster.js';
|
||||
import { SecretBundle } from './classes.secretbundle.js';
|
||||
import { SecretGroup } from './classes.secretgroup.js';
|
||||
import { ExternalRegistry } from './classes.externalregistry.js';
|
||||
|
||||
export class CloudlyApiClient {
|
||||
private cloudlyUrl: string;
|
||||
private registerAs: string;
|
||||
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
public typedsocketClient: plugins.typedsocket.TypedSocket;
|
||||
|
||||
// Subjects
|
||||
public configUpdateSubject = new plugins.smartrx.rxjs.Subject<
|
||||
plugins.servezoneInterfaces.requests.config.IRequest_Cloudly_Coreflow_PushClusterConfig['request']
|
||||
>();
|
||||
|
||||
public serverActionSubject = new plugins.smartrx.rxjs.Subject<
|
||||
plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction['request']
|
||||
>();
|
||||
|
||||
constructor(optionsArg: {
|
||||
registerAs: TClientType;
|
||||
cloudlyUrl?: string;
|
||||
}) {
|
||||
this.registerAs = optionsArg.registerAs;
|
||||
this.cloudlyUrl =
|
||||
optionsArg?.cloudlyUrl || process.env.CLOUDLY_URL || 'https://cloudly.layer.io:443';
|
||||
|
||||
console.log(
|
||||
`creating LoleCloudlyClient: registering as ${this.registerAs} and target url ${this.cloudlyUrl}`
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Cloudly_Coreflow_PushClusterConfig>(
|
||||
new plugins.typedrequest.TypedHandler('pushClusterConfig', async (dataArg) => {
|
||||
this.configUpdateSubject.next(dataArg);
|
||||
return {};
|
||||
})
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction>(
|
||||
new plugins.typedrequest.TypedHandler('triggerServerAction', async (dataArg) => {
|
||||
this.serverActionSubject.next(dataArg);
|
||||
return {
|
||||
actionConfirmed: true,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
this.typedsocketClient = await plugins.typedsocket.TypedSocket.createClient(
|
||||
this.typedrouter,
|
||||
this.cloudlyUrl
|
||||
);
|
||||
console.log(
|
||||
`CloudlyClient connected to cloudly at ${this.cloudlyUrl}. Remember to get an identity.`
|
||||
);
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
await this.typedsocketClient.stop();
|
||||
}
|
||||
|
||||
public identity: plugins.servezoneInterfaces.data.IIdentity;
|
||||
public async getIdentityByToken(
|
||||
token: string,
|
||||
optionsArg?: {
|
||||
tagConnection?: boolean;
|
||||
statefullIdentity?: boolean;
|
||||
}
|
||||
): Promise<plugins.servezoneInterfaces.data.IIdentity> {
|
||||
optionsArg = Object.assign({}, {
|
||||
tagConnection: false,
|
||||
statefullIdentity: true,
|
||||
}, optionsArg);
|
||||
|
||||
const identityRequest =
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken>(
|
||||
'getIdentityByToken'
|
||||
);
|
||||
console.log(`trying to get identity from cloudly with supplied jumpCodeArg: ${token}`);
|
||||
const response = await identityRequest.fire({
|
||||
token: token,
|
||||
});
|
||||
console.log('got identity response');
|
||||
const identity = response.identity;
|
||||
|
||||
if (optionsArg.tagConnection) {
|
||||
this.typedsocketClient.addTag('identity', identity);
|
||||
}
|
||||
|
||||
if (optionsArg.statefullIdentity) {
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
return identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* will use statefull identity by default
|
||||
*/
|
||||
public async getClusterConfigFromCloudlyByIdentity(
|
||||
identityArg: plugins.servezoneInterfaces.data.IIdentity = this.identity
|
||||
): Promise<plugins.servezoneInterfaces.data.ICluster> {
|
||||
const clusterConfigRequest =
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>(
|
||||
'getClusterConfig'
|
||||
);
|
||||
const response = await clusterConfigRequest.fire({
|
||||
identity: identityArg,
|
||||
});
|
||||
return response.configData;
|
||||
}
|
||||
|
||||
/**
|
||||
* will use statefull identity by default
|
||||
*/
|
||||
public async getServerConfigFromCloudlyByIdentity(
|
||||
identityArg: plugins.servezoneInterfaces.data.IIdentity = this.identity
|
||||
): Promise<plugins.servezoneInterfaces.data.IServer> {
|
||||
const serverConfigRequest =
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetServerConfig>(
|
||||
'getServerConfig'
|
||||
);
|
||||
const response = await serverConfigRequest.fire({
|
||||
identity: identityArg,
|
||||
serverId: '', // TODO: get server id here
|
||||
});
|
||||
return response.configData;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a certificate for a domain used by a service
|
||||
*/
|
||||
public async getCertificateForDomain(optionsArg: {
|
||||
domainName: string;
|
||||
type: plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain['request']['type'];
|
||||
identity?: plugins.servezoneInterfaces.data.IIdentity;
|
||||
}): Promise<plugins.tsclass.network.ICert> {
|
||||
optionsArg.identity = optionsArg.identity || this.identity;
|
||||
if (!optionsArg.identity) {
|
||||
throw new Error('identity is required. Either provide one or login first.');
|
||||
}
|
||||
const typedCertificateRequest =
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain>(
|
||||
'getCertificateForDomain'
|
||||
);
|
||||
const typedResponse = await typedCertificateRequest.fire({
|
||||
identity: this.identity, // do proper auth here
|
||||
domainName: optionsArg.domainName,
|
||||
type: optionsArg.type,
|
||||
});
|
||||
return typedResponse.certificate;
|
||||
}
|
||||
|
||||
public externalRegistry = {
|
||||
// ExternalRegistry
|
||||
getRegistryById: async (registryNameArg: string) => {
|
||||
return ExternalRegistry.getExternalRegistryById(this, registryNameArg);
|
||||
},
|
||||
getRegistries: async () => {
|
||||
return ExternalRegistry.getExternalRegistries(this);
|
||||
},
|
||||
createRegistry: async (optionsArg: Parameters<typeof ExternalRegistry.createExternalRegistry>[1]) => {
|
||||
return ExternalRegistry.createExternalRegistry(this, optionsArg);
|
||||
}
|
||||
}
|
||||
|
||||
public image = {
|
||||
// Images
|
||||
getImageById: async (imageIdArg: string) => {
|
||||
return Image.getImageById(this, imageIdArg);
|
||||
},
|
||||
getImages: async () => {
|
||||
return Image.getImages(this);
|
||||
},
|
||||
createImage: async (optionsArg: Parameters<typeof Image.createImage>[1]) => {
|
||||
return Image.createImage(this, optionsArg);
|
||||
}
|
||||
}
|
||||
|
||||
public services = {
|
||||
// Services
|
||||
getServiceById: async (serviceIdArg: string) => {
|
||||
return Service.getServiceById(this, serviceIdArg);
|
||||
},
|
||||
getServices: async () => {
|
||||
return Service.getServices(this);
|
||||
},
|
||||
createService: async (optionsArg: Parameters<typeof Service.createService>[1]) => {
|
||||
return Service.createService(this, optionsArg);
|
||||
}
|
||||
}
|
||||
|
||||
public cluster = {
|
||||
// Clusters
|
||||
getClusterById: async (clusterIdArg: string) => {
|
||||
return Cluster.getClusterById(this, clusterIdArg);
|
||||
},
|
||||
getClusters: async () => {
|
||||
return Cluster.getClusters(this);
|
||||
},
|
||||
createCluster: async (optionsArg: Parameters<typeof Cluster.createCluster>[1]) => {
|
||||
return Cluster.createCluster(this, optionsArg);
|
||||
}
|
||||
}
|
||||
|
||||
public secretbundle = {
|
||||
// SecretBundles
|
||||
getSecretBundleById: async (secretBundleIdArg: string) => {
|
||||
return SecretBundle.getSecretBundleById(this, secretBundleIdArg);
|
||||
},
|
||||
getSecretBundles: async () => {
|
||||
return SecretBundle.getSecretBundles(this);
|
||||
},
|
||||
createSecretBundle: async (optionsArg: Parameters<typeof SecretBundle.createSecretBundle>[1]) => {
|
||||
return SecretBundle.createSecretBundle(this, optionsArg);
|
||||
}
|
||||
}
|
||||
|
||||
public secretgroup = {
|
||||
// SecretGroups
|
||||
getSecretGroupById: async (secretGroupIdArg: string) => {
|
||||
return SecretGroup.getSecretGroupById(this, secretGroupIdArg);
|
||||
},
|
||||
getSecretGroups: async () => {
|
||||
return SecretGroup.getSecretGroups(this);
|
||||
},
|
||||
createSecretGroup: async (optionsArg: Parameters<typeof SecretGroup.createSecretGroup>[1]) => {
|
||||
return SecretGroup.createSecretGroup(this, optionsArg);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export type TClientType = 'coreflow' | 'cli' | 'serverconfig';
|
||||
|
||||
export class CloudlyClient {
|
||||
private cloudlyUrl: string;
|
||||
private registerAs: string;
|
||||
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
public typedsocketClient: plugins.typedsocket.TypedSocket;
|
||||
|
||||
// Subjects
|
||||
public configUpdateSubject = new plugins.smartrx.rxjs.Subject<
|
||||
plugins.servezoneInterfaces.requests.config.IRequest_Cloudly_Coreflow_PushClusterConfig['request']
|
||||
>();
|
||||
|
||||
public serverActionSubject = new plugins.smartrx.rxjs.Subject<
|
||||
plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction['request']
|
||||
>();
|
||||
|
||||
constructor(registerAsArg: TClientType) {
|
||||
this.cloudlyUrl = process.env.CLOUDLY_URL || 'https://cloudly.layer.io:443';
|
||||
this.registerAs = registerAsArg;
|
||||
|
||||
console.log(
|
||||
`creating LoleCloudlyClient: registering as ${this.registerAs} and target url ${this.cloudlyUrl}`
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Cloudly_Coreflow_PushClusterConfig>(
|
||||
new plugins.typedrequest.TypedHandler('pushClusterConfig', async (dataArg) => {
|
||||
this.configUpdateSubject.next(dataArg);
|
||||
return {};
|
||||
})
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Cloudly_Coreflow_PushClusterConfig>(
|
||||
new plugins.typedrequest.TypedHandler('pushClusterConfig', async (dataArg) => {
|
||||
this.configUpdateSubject.next(dataArg);
|
||||
return {};
|
||||
})
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction>(
|
||||
new plugins.typedrequest.TypedHandler('triggerServerAction', async (dataArg) => {
|
||||
this.serverActionSubject.next(dataArg);
|
||||
return {
|
||||
actionConfirmed: true,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
this.typedsocketClient = await plugins.typedsocket.TypedSocket.createClient(
|
||||
this.typedrouter,
|
||||
this.cloudlyUrl
|
||||
);
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
await this.typedsocketClient.stop();
|
||||
}
|
||||
|
||||
public async getIdentityByJumpCode(
|
||||
jumpCodeArg: string,
|
||||
tagConnection = false
|
||||
): Promise<plugins.servezoneInterfaces.data.IClusterIdentifier> {
|
||||
const identityRequest =
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByJumpCode>(
|
||||
'getIdentityByJumpCode'
|
||||
);
|
||||
console.log(`trying to get identity from cloudly with supplied jumpCodeArg: ${jumpCodeArg}`);
|
||||
const response = await identityRequest.fire({
|
||||
jumpCode: jumpCodeArg,
|
||||
});
|
||||
console.log('got identity response');
|
||||
const identity = response.clusterIdentifier;
|
||||
|
||||
if (tagConnection) {
|
||||
this.typedsocketClient.addTag('identity', identity);
|
||||
}
|
||||
|
||||
return identity;
|
||||
}
|
||||
|
||||
public async getClusterConfigFromCloudlyByIdentity(
|
||||
identityArg: plugins.servezoneInterfaces.data.IClusterIdentifier
|
||||
): Promise<plugins.servezoneInterfaces.data.ICluster> {
|
||||
const clusterConfigRequest =
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>(
|
||||
'getClusterConfig'
|
||||
);
|
||||
const response = await clusterConfigRequest.fire({
|
||||
jwt: '', // TODO: do proper auth here
|
||||
clusterIdentifier: identityArg,
|
||||
});
|
||||
return response.configData;
|
||||
}
|
||||
|
||||
public async getServerConfigFromCloudlyByIdentity(
|
||||
identityArg: plugins.servezoneInterfaces.data.IClusterIdentifier
|
||||
): Promise<plugins.servezoneInterfaces.data.IServer> {
|
||||
const serverConfigRequest =
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetServerConfig>(
|
||||
'getServerConfig'
|
||||
);
|
||||
const response = await serverConfigRequest.fire({
|
||||
jwt: '', // TODO: do proper auth here
|
||||
serverId: '' // TODO: get server id here
|
||||
});
|
||||
return response.configData;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a certificate for a domain used by a service
|
||||
* @param serviceNameArg
|
||||
* @param domainNameArg
|
||||
*/
|
||||
public async getCertificateForDomainOverHttps(domainNameArg: string): Promise<plugins.tsclass.network.ICert> {
|
||||
const typedCertificateRequest =
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetSslCertificate>(
|
||||
'getSslCertificate'
|
||||
);
|
||||
const typedResponse = await typedCertificateRequest.fire({
|
||||
authToken: '', // do proper auth here
|
||||
requiredCertName: domainNameArg,
|
||||
});
|
||||
return typedResponse.certificate;
|
||||
}
|
||||
}
|
@ -1,6 +1,84 @@
|
||||
import { CloudlyApiClient } from './classes.cloudlyapiclient.js';
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export class Cluster {
|
||||
public getServers() {}
|
||||
|
||||
export class Cluster implements plugins.servezoneInterfaces.data.ICluster {
|
||||
// STATIC
|
||||
public static async getClusterById(cloudlyClientRef: CloudlyApiClient, clusterIdArg: string) {
|
||||
const getClusterByIdTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.cluster.IReq_Any_Cloudly_GetClusterById>(
|
||||
'getClusterById'
|
||||
);
|
||||
const response = await getClusterByIdTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
clusterId: clusterIdArg,
|
||||
});
|
||||
const newCluster = new Cluster(cloudlyClientRef);
|
||||
Object.assign(newCluster, response.cluster);
|
||||
return newCluster;
|
||||
}
|
||||
|
||||
public static async getClusters(cloudlyClientRef: CloudlyApiClient) {
|
||||
const getClustersTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.cluster.IReq_Any_Cloudly_GetClusters>(
|
||||
'getClusters'
|
||||
);
|
||||
const response = await getClustersTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
});
|
||||
const clusterConfigs: Cluster[] = [];
|
||||
for (const clusterConfig of response.clusters) {
|
||||
const newCluster = new Cluster(cloudlyClientRef);
|
||||
Object.assign(newCluster, clusterConfig);
|
||||
clusterConfigs.push(newCluster);
|
||||
}
|
||||
return clusterConfigs;
|
||||
}
|
||||
|
||||
public static async createCluster(cloudlyClientRef: CloudlyApiClient, clusterNameArg: string) {
|
||||
const createClusterTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.cluster.IRequest_CreateCluster>(
|
||||
'createCluster'
|
||||
);
|
||||
const response = await createClusterTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
clusterName: clusterNameArg,
|
||||
});
|
||||
const newCluster = new Cluster(cloudlyClientRef);
|
||||
Object.assign(newCluster, response.cluster);
|
||||
return newCluster;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
public id: string;
|
||||
public data: plugins.servezoneInterfaces.data.ICluster['data'];
|
||||
public cloudlyClientRef: CloudlyApiClient;
|
||||
|
||||
constructor(cloudlyClientRef: CloudlyApiClient) {
|
||||
this.cloudlyClientRef = cloudlyClientRef;
|
||||
}
|
||||
|
||||
|
||||
public async update() {
|
||||
const updateClusterTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.cluster.IReq_Any_Cloudly_UpdateCluster>(
|
||||
'updateCluster'
|
||||
);
|
||||
const response = await updateClusterTR.fire({
|
||||
identity: this.cloudlyClientRef.identity,
|
||||
clusterData: this.data,
|
||||
});
|
||||
|
||||
const resultClusterData = response.resultCluster.data;
|
||||
plugins.smartexpect.expect(resultClusterData).toEqual(this.data);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public async delete(cloudlyClientRef: CloudlyApiClient, clusterIdArg: string) {
|
||||
const deleteClusterTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.cluster.IReq_Any_Cloudly_DeleteClusterById>(
|
||||
'deleteClusterById'
|
||||
);
|
||||
const response = await deleteClusterTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
clusterId: this.id,
|
||||
});
|
||||
plugins.smartexpect.expect(response.ok).toBeTrue();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
83
ts_apiclient/classes.externalregistry.ts
Normal file
83
ts_apiclient/classes.externalregistry.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import type { CloudlyApiClient } from './classes.cloudlyapiclient.js';
|
||||
|
||||
export class ExternalRegistry implements plugins.servezoneInterfaces.data.IExternalRegistry {
|
||||
// STATIC
|
||||
public static async getExternalRegistryById(cloudlyClientRef: CloudlyApiClient, registryNameArg: string) {
|
||||
const getRegistryByIdTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.externalRegistry.IReq_GetRegistryById>(
|
||||
'getExternalRegistryById'
|
||||
);
|
||||
const response = await getRegistryByIdTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
id: registryNameArg,
|
||||
});
|
||||
const newRegistry = new ExternalRegistry(cloudlyClientRef);
|
||||
Object.assign(newRegistry, response.registry);
|
||||
return newRegistry;
|
||||
}
|
||||
|
||||
public static async getExternalRegistries(cloudlyClientRef: CloudlyApiClient) {
|
||||
const getRegistriesTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.externalRegistry.IReq_GetRegistries>(
|
||||
'getExternalRegistries'
|
||||
);
|
||||
const response = await getRegistriesTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
});
|
||||
const registryConfigs: ExternalRegistry[] = [];
|
||||
for (const registryConfig of response.registries) {
|
||||
const newRegistry = new ExternalRegistry(cloudlyClientRef);
|
||||
Object.assign(newRegistry, registryConfig);
|
||||
registryConfigs.push(newRegistry);
|
||||
}
|
||||
return registryConfigs;
|
||||
}
|
||||
|
||||
public static async createExternalRegistry(cloudlyClientRef: CloudlyApiClient, registryDataArg: Partial<plugins.servezoneInterfaces.data.IExternalRegistry['data']>) {
|
||||
const createRegistryTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.externalRegistry.IReq_CreateRegistry>(
|
||||
'createExternalRegistry'
|
||||
);
|
||||
const response = await createRegistryTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
registryData: registryDataArg as plugins.servezoneInterfaces.data.IExternalRegistry['data'],
|
||||
});
|
||||
const newRegistry = new ExternalRegistry(cloudlyClientRef);
|
||||
Object.assign(newRegistry, response.registry);
|
||||
return newRegistry;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
public id: string;
|
||||
public data: plugins.servezoneInterfaces.data.IExternalRegistry['data'];
|
||||
public cloudlyClientRef: CloudlyApiClient;
|
||||
|
||||
constructor(cloudlyClientRef: CloudlyApiClient) {
|
||||
this.cloudlyClientRef = cloudlyClientRef;
|
||||
}
|
||||
|
||||
public async update() {
|
||||
const updateRegistryTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.externalRegistry.IReq_UpdateRegistry>(
|
||||
'updateExternalRegistry'
|
||||
);
|
||||
const response = await updateRegistryTR.fire({
|
||||
identity: this.cloudlyClientRef.identity,
|
||||
registryData: this.data,
|
||||
});
|
||||
|
||||
const resultRegistryData = response.resultRegistry.data;
|
||||
plugins.smartexpect.expect(resultRegistryData).toEqual(this.data);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public async delete(cloudlyClientRef: CloudlyApiClient, registryIdArg: string) {
|
||||
const deleteRegistryTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.externalRegistry.IReq_DeleteRegistryById>(
|
||||
'deleteExternalRegistryById'
|
||||
);
|
||||
const response = await deleteRegistryTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
registryId: this.id,
|
||||
});
|
||||
plugins.smartexpect.expect(response.ok).toBeTrue();
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,5 +1,113 @@
|
||||
import type { CloudlyApiClient } from './classes.cloudlyapiclient.js';
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export class Image {
|
||||
public getImages() {}
|
||||
}
|
||||
export class Image implements plugins.servezoneInterfaces.data.IImage {
|
||||
public static async getImages(cloudlyClientRef: CloudlyApiClient) {
|
||||
const getAllImagesTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
|
||||
'getAllImages'
|
||||
);
|
||||
const response = await getAllImagesTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
});
|
||||
const resultImages: Image[] = [];
|
||||
for (const image of response.images) {
|
||||
const newImage = new Image(cloudlyClientRef);
|
||||
Object.assign(newImage, image);
|
||||
resultImages.push(newImage);
|
||||
}
|
||||
return resultImages;
|
||||
}
|
||||
|
||||
public static async getImageById(cloudlyClientRef: CloudlyApiClient, imageIdArg: string) {
|
||||
const getImageByIdTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_GetImage>(
|
||||
'getImage'
|
||||
);
|
||||
const response = await getImageByIdTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
imageId: imageIdArg,
|
||||
});
|
||||
const newImage = new Image(cloudlyClientRef);
|
||||
Object.assign(newImage, response.image);
|
||||
return newImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a new image
|
||||
*/
|
||||
public static async createImage(cloudlyClientRef: CloudlyApiClient, imageDataArg: Partial<plugins.servezoneInterfaces.data.IImage['data']>) {
|
||||
const createImageTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
|
||||
'createImage'
|
||||
);
|
||||
const response = await createImageTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
name: imageDataArg.name,
|
||||
description: imageDataArg.description,
|
||||
});
|
||||
const newImage = new Image(cloudlyClientRef);
|
||||
Object.assign(newImage, response.image);
|
||||
return newImage;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
cloudlyClientRef: CloudlyApiClient;
|
||||
|
||||
id: plugins.servezoneInterfaces.data.IImage['id'];
|
||||
data: plugins.servezoneInterfaces.data.IImage['data'];
|
||||
|
||||
constructor(cloudlyClientRef: CloudlyApiClient) {
|
||||
this.cloudlyClientRef = cloudlyClientRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* updates the image data
|
||||
*/
|
||||
public async update() {
|
||||
const getVersionsTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_GetImage>(
|
||||
'getImage'
|
||||
);
|
||||
const response = await getVersionsTR.fire({
|
||||
identity: this.cloudlyClientRef.identity,
|
||||
imageId: this.id,
|
||||
});
|
||||
Object.assign(this, response.image);
|
||||
}
|
||||
|
||||
/**
|
||||
* pushes a new version of the image
|
||||
* @param imageVersion
|
||||
* @param imageReadableArg
|
||||
*/
|
||||
public async pushImageVersion(imageVersion: string, imageReadableArg: ReadableStream<Uint8Array>): Promise<void> {
|
||||
const done = plugins.smartpromise.defer();
|
||||
const pushImageTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_PushImageVersion>(
|
||||
'pushImageVersion'
|
||||
);
|
||||
const virtualStream = new plugins.typedrequest.VirtualStream();
|
||||
const response = await pushImageTR.fire({
|
||||
identity: this.cloudlyClientRef.identity,
|
||||
imageId: this.id,
|
||||
versionString: '',
|
||||
imageStream: virtualStream,
|
||||
});
|
||||
await virtualStream.readFromWebstream(imageReadableArg);
|
||||
await this.update();
|
||||
};
|
||||
|
||||
/**
|
||||
* pulls a version of the image
|
||||
*/
|
||||
public async pullImageVersion(versionStringArg: string): Promise<ReadableStream<Uint8Array>> {
|
||||
const pullImageTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_PullImageVersion>(
|
||||
'pullImageVersion'
|
||||
);
|
||||
const response = await pullImageTR.fire({
|
||||
identity: this.cloudlyClientRef.identity,
|
||||
imageId: this.id,
|
||||
versionString: versionStringArg,
|
||||
});
|
||||
const imageStream = response.imageStream;
|
||||
const webduplexStream = new plugins.webstream.WebDuplexStream({});
|
||||
imageStream.writeToWebstream(webduplexStream.writable);
|
||||
return webduplexStream.readable;
|
||||
};
|
||||
}
|
||||
|
135
ts_apiclient/classes.secretbundle.ts
Normal file
135
ts_apiclient/classes.secretbundle.ts
Normal file
@ -0,0 +1,135 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import type { CloudlyApiClient } from './classes.cloudlyapiclient.js';
|
||||
import { SecretGroup } from './classes.secretgroup.js';
|
||||
|
||||
export class SecretBundle implements plugins.servezoneInterfaces.data.ISecretBundle {
|
||||
// STATIC
|
||||
public static async getSecretBundleById(cloudlyClientRef: CloudlyApiClient, secretBundleIdArg: string) {
|
||||
const getSecretBundleByIdTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetSecretBundleById>(
|
||||
'getSecretBundleById'
|
||||
);
|
||||
const response = await getSecretBundleByIdTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
secretBundleId: secretBundleIdArg,
|
||||
});
|
||||
const newSecretBundle = new SecretBundle(cloudlyClientRef);
|
||||
Object.assign(newSecretBundle, response.secretBundle);
|
||||
return newSecretBundle;
|
||||
}
|
||||
|
||||
public static async getSecretBundleByAuthorization(cloudlyClientRef: CloudlyApiClient, secretBundleAuthorizationArg: plugins.servezoneInterfaces.data.ISecretBundleAuthorization) {
|
||||
const getSecretBundleByAuthorizationTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetSecretBundleByAuthorization>(
|
||||
'getSecretBundleByAuthorization'
|
||||
);
|
||||
const response = await getSecretBundleByAuthorizationTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
secretBundleAuthorization: secretBundleAuthorizationArg,
|
||||
});
|
||||
const newSecretBundle = new SecretBundle(cloudlyClientRef);
|
||||
Object.assign(newSecretBundle, response.secretBundle);
|
||||
return newSecretBundle;
|
||||
}
|
||||
|
||||
public static async getSecretBundles(cloudlyClientRef: CloudlyApiClient) {
|
||||
const getSecretBundlesTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetSecretBundles>(
|
||||
'getSecretBundles'
|
||||
);
|
||||
const response = await getSecretBundlesTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
});
|
||||
const secretBundles: SecretBundle[] = [];
|
||||
for (const secretBundle of response.secretBundles) {
|
||||
const newSecretBundle = new SecretBundle(cloudlyClientRef);
|
||||
Object.assign(newSecretBundle, secretBundle);
|
||||
secretBundles.push(newSecretBundle);
|
||||
}
|
||||
return secretBundles;
|
||||
}
|
||||
|
||||
public static async createSecretBundle(cloudlyClientRef: CloudlyApiClient, secretBundleDataArg: Partial<plugins.servezoneInterfaces.data.ISecretBundle['data']>) {
|
||||
const createSecretBundleTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_CreateSecretBundle>(
|
||||
'createSecretBundle'
|
||||
);
|
||||
const response = await createSecretBundleTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
secretBundle: {
|
||||
id: null,
|
||||
data: {
|
||||
name: secretBundleDataArg.name,
|
||||
description: secretBundleDataArg.description,
|
||||
type: secretBundleDataArg.type,
|
||||
authorizations: secretBundleDataArg.authorizations,
|
||||
imageClaims: secretBundleDataArg.imageClaims,
|
||||
includedSecretGroupIds: secretBundleDataArg.includedSecretGroupIds,
|
||||
includedTags: secretBundleDataArg.includedTags,
|
||||
},
|
||||
},
|
||||
});
|
||||
const newSecretBundle = new SecretBundle(cloudlyClientRef);
|
||||
Object.assign(newSecretBundle, response.resultSecretBundle);
|
||||
return newSecretBundle;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
|
||||
public cloudlyClientRef: CloudlyApiClient;
|
||||
|
||||
public id: string;
|
||||
public data: plugins.servezoneInterfaces.data.ISecretBundle['data'];
|
||||
|
||||
constructor(cloudlyClientRef: CloudlyApiClient) {
|
||||
this.cloudlyClientRef = cloudlyClientRef;
|
||||
}
|
||||
|
||||
public async update() {
|
||||
const updateSecretBundleTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_UpdateSecretBundle>(
|
||||
'updateSecretBundle'
|
||||
);
|
||||
const response = await updateSecretBundleTR.fire({
|
||||
identity: this.cloudlyClientRef.identity,
|
||||
secretBundle: {
|
||||
id: this.id,
|
||||
data: this.data,
|
||||
},
|
||||
});
|
||||
|
||||
const resultSecretBundleData = response.resultSecretBundle.data;
|
||||
plugins.smartexpect.expect(resultSecretBundleData).toEqual(this.data);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public async delete(cloudlyClientRef: CloudlyApiClient, secretBundleIdArg: string) {
|
||||
const deleteSecretBundleTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_DeleteSecretBundleById>(
|
||||
'deleteSecretBundleById'
|
||||
);
|
||||
const response = await deleteSecretBundleTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
secretBundleId: this.id,
|
||||
});
|
||||
plugins.smartexpect.expect(response.ok).toBeTrue();
|
||||
return null;
|
||||
}
|
||||
|
||||
public async getFlatKeyValueObjectForEnvironment(environmentArg: string = 'production') {
|
||||
const bundleAuthorization = this.data.authorizations.find(authorization => {
|
||||
return authorization.environment === environmentArg;
|
||||
});
|
||||
if (bundleAuthorization) {
|
||||
throw new Error(`no matching environment >>${environmentArg} found in secret bundle`);
|
||||
}
|
||||
|
||||
const getFlatKeyValueObjectTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetFlatKeyValueObject>(
|
||||
'getFlatKeyValueObject'
|
||||
);
|
||||
const response = await getFlatKeyValueObjectTR.fire({
|
||||
identity: this.cloudlyClientRef.identity,
|
||||
seccretBundleId: this.id,
|
||||
secretBundleAuthorization: bundleAuthorization,
|
||||
});
|
||||
|
||||
const flatKeyValueObject: {[key: string]: string} = response.flatKeyValueObject;
|
||||
|
||||
return flatKeyValueObject;
|
||||
}
|
||||
}
|
96
ts_apiclient/classes.secretgroup.ts
Normal file
96
ts_apiclient/classes.secretgroup.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import type { CloudlyApiClient } from './classes.cloudlyapiclient.js';
|
||||
|
||||
export class SecretGroup implements plugins.servezoneInterfaces.data.ISecretGroup {
|
||||
public cloudlyClientRef: CloudlyApiClient;
|
||||
|
||||
public id: string;
|
||||
public data: plugins.servezoneInterfaces.data.ISecretGroup['data'];
|
||||
|
||||
constructor(cloudlyClientRef: CloudlyApiClient) {
|
||||
this.cloudlyClientRef = cloudlyClientRef;
|
||||
}
|
||||
|
||||
public static async getSecretGroupById(cloudlyClientRef: CloudlyApiClient, secretGroupIdArg: string) {
|
||||
const getSecretGroupByIdTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretgroup.IReq_GetSecretGroupById>(
|
||||
'getSecretGroupById'
|
||||
);
|
||||
const response = await getSecretGroupByIdTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
secretGroupId: secretGroupIdArg,
|
||||
});
|
||||
const newSecretGroup = new SecretGroup(cloudlyClientRef);
|
||||
Object.assign(newSecretGroup, response.secretGroup);
|
||||
return newSecretGroup;
|
||||
}
|
||||
|
||||
public static async getSecretGroups(cloudlyClientRef: CloudlyApiClient) {
|
||||
const getSecretGroupsTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretgroup.IReq_GetSecretGroups>(
|
||||
'getSecretGroups'
|
||||
);
|
||||
const response = await getSecretGroupsTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
});
|
||||
const secretGroups: SecretGroup[] = [];
|
||||
for (const secretGroup of response.secretGroups) {
|
||||
const newSecretGroup = new SecretGroup(cloudlyClientRef);
|
||||
Object.assign(newSecretGroup, secretGroup);
|
||||
secretGroups.push(newSecretGroup);
|
||||
}
|
||||
return secretGroups;
|
||||
}
|
||||
|
||||
public static async createSecretGroup(cloudlyClientRef: CloudlyApiClient, secretGroupDataArg: Partial<plugins.servezoneInterfaces.data.ISecretGroup['data']>) {
|
||||
const createSecretGroupTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretgroup.IReq_CreateSecretGroup>(
|
||||
'createSecretGroup'
|
||||
);
|
||||
const response = await createSecretGroupTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
secretGroup: {
|
||||
id: null,
|
||||
data: {
|
||||
name: secretGroupDataArg.name,
|
||||
description: secretGroupDataArg.description,
|
||||
environments: secretGroupDataArg.environments,
|
||||
key: secretGroupDataArg.key,
|
||||
tags: secretGroupDataArg.tags,
|
||||
priority: secretGroupDataArg.priority,
|
||||
},
|
||||
},
|
||||
});
|
||||
const newSecretGroup = new SecretGroup(cloudlyClientRef);
|
||||
Object.assign(newSecretGroup, response.resultSecretGroup);
|
||||
return newSecretGroup;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
public async update() {
|
||||
const updateSecretGroupTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretgroup.IReq_UpdateSecretGroup>(
|
||||
'updateSecretGroup'
|
||||
);
|
||||
const response = await updateSecretGroupTR.fire({
|
||||
identity: this.cloudlyClientRef.identity,
|
||||
secretGroup: {
|
||||
id: this.id,
|
||||
data: this.data,
|
||||
},
|
||||
});
|
||||
|
||||
const resultSecretGroupData = response.resultSecretGroup.data;
|
||||
plugins.smartexpect.expect(resultSecretGroupData).toEqual(this.data);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public async delete(cloudlyClientRef: CloudlyApiClient, secretGroupIdArg: string) {
|
||||
const deleteSecretGroupTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretgroup.IReq_DeleteSecretGroupById>(
|
||||
'deleteSecretGroupById'
|
||||
);
|
||||
const response = await deleteSecretGroupTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
secretGroupId: this.id,
|
||||
});
|
||||
plugins.smartexpect.expect(response.ok).toBeTrue();
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export class Server {
|
||||
public static getServers() {
|
||||
|
||||
}
|
||||
}
|
@ -1,5 +1,78 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import type { CloudlyApiClient } from './classes.cloudlyapiclient.js';
|
||||
|
||||
export class Service {
|
||||
export class Service implements plugins.servezoneInterfaces.data.IService {
|
||||
public static async getServices(cloudlyClientRef: CloudlyApiClient) {
|
||||
const getAllServicesTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_GetServices>(
|
||||
'getServices'
|
||||
);
|
||||
const response = await getAllServicesTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
});
|
||||
const resultServices: Service[] = [];
|
||||
for (const service of response.services) {
|
||||
const newService = new Service(cloudlyClientRef);
|
||||
Object.assign(newService, service);
|
||||
resultServices.push(newService);
|
||||
}
|
||||
return resultServices;
|
||||
}
|
||||
|
||||
public static async getServiceById(cloudlyClientRef: CloudlyApiClient, serviceIdArg: string) {
|
||||
const getServiceByIdTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_GetServiceById>(
|
||||
'getServiceById'
|
||||
);
|
||||
const response = await getServiceByIdTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
serviceId: serviceIdArg,
|
||||
});
|
||||
const newService = new Service(cloudlyClientRef);
|
||||
Object.assign(newService, response.service);
|
||||
return newService;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a new service
|
||||
*/
|
||||
public static async createService(cloudlyClientRef: CloudlyApiClient, serviceDataArg: Partial<plugins.servezoneInterfaces.data.IService['data']>) {
|
||||
const createServiceTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_CreateService>(
|
||||
'createService'
|
||||
);
|
||||
const response = await createServiceTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
serviceData: serviceDataArg as plugins.servezoneInterfaces.data.IService['data'],
|
||||
});
|
||||
const newService = new Service(cloudlyClientRef);
|
||||
Object.assign(newService, response.service);
|
||||
return newService;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
cloudlyClientRef: CloudlyApiClient;
|
||||
|
||||
public id: string;
|
||||
public data: plugins.servezoneInterfaces.data.IService['data'];
|
||||
|
||||
constructor(cloudlyClientRef: CloudlyApiClient) {
|
||||
this.cloudlyClientRef = cloudlyClientRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* The service has a secret bundle.
|
||||
* This function essentially returns the secret bundle as a flat object.
|
||||
* In other words, it resolves secret groups and
|
||||
*/
|
||||
public async getSecretBundleAsFlatObject(environmentArg: string = 'production') {
|
||||
const getServiceSecretBundlesAsFlatObjectTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_GetServiceSecretBundlesAsFlatObject>(
|
||||
'getServiceSecretBundlesAsFlatObject'
|
||||
);
|
||||
const response = await getServiceSecretBundlesAsFlatObjectTR.fire({
|
||||
identity: this.cloudlyClientRef.identity,
|
||||
serviceId: this.id,
|
||||
environment: environmentArg,
|
||||
});
|
||||
const flatKeyValueObject: {[key: string]: string} = response.flatKeyValueObject;
|
||||
|
||||
return flatKeyValueObject;
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
export * from './classes.cloudlyclient.js';
|
||||
export * from './classes.cloudlyapiclient.js';
|
@ -6,10 +6,16 @@ export {
|
||||
}
|
||||
|
||||
// @push.rocks scope
|
||||
import * as smartexpect from '@push.rocks/smartexpect';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartrx from '@push.rocks/smartrx';
|
||||
import * as webstream from '@push.rocks/smartstream/web';
|
||||
|
||||
export {
|
||||
smartexpect,
|
||||
smartpromise,
|
||||
smartrx,
|
||||
webstream,
|
||||
}
|
||||
|
||||
// @api.global scope
|
||||
|
202
ts_apiclient/readme.md
Normal file
202
ts_apiclient/readme.md
Normal file
@ -0,0 +1,202 @@
|
||||
# @serve.zone/api
|
||||
|
||||
The `@serve.zone/api` module is a robust and versatile API client, designed to facilitate seamless communication with various cloud resources managed by the Cloudly platform. This API client extends a rich set of functionalities, offering developers a comprehensive and programmable interface for interacting with their multi-cloud infrastructure.
|
||||
|
||||
## Install
|
||||
|
||||
To install the `@serve.zone/api` package, execute the following command in your terminal:
|
||||
|
||||
```bash
|
||||
npm install @serve.zone/api --save
|
||||
```
|
||||
|
||||
This command will download the module and add it to your project's `package.json` dependencies, allowing you to utilize its capabilities within your application.
|
||||
|
||||
## Usage
|
||||
|
||||
The `@serve.zone/api` client is tailored to handle various operations within a multi-cloud environment efficiently. Throughout this section, we will explore the different features and use-cases of this API client, aiding you in leveraging its full potential.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before integrating `@serve.zone/api` into your project, ensure the following prerequisites are satisfied:
|
||||
- You have Node.js installed on your system (preferably the latest Long-Term Support version).
|
||||
- You're utilizing a TypeScript-compatible environment for development.
|
||||
|
||||
### Establishing an API Client Instance
|
||||
|
||||
The cornerstone of using `@serve.zone/api` is initializing a `CloudlyApiClient` instance. It serves as the main point of interaction, enabling communication with underlying cloud infrastructures managed by Cloudly. Here's a basic setup guide:
|
||||
|
||||
```typescript
|
||||
import { CloudlyApiClient, TClientType } from '@serve.zone/api';
|
||||
|
||||
async function initializeClient() {
|
||||
const client = new CloudlyApiClient({
|
||||
registerAs: 'api' as TClientType,
|
||||
cloudlyUrl: 'https://yourcloudly.url:443'
|
||||
});
|
||||
|
||||
await client.start();
|
||||
return client;
|
||||
}
|
||||
|
||||
const cloudlyClient = await initializeClient();
|
||||
```
|
||||
|
||||
The above code initializes the `CloudlyApiClient` object, connecting your application to the configured Cloudly environment.
|
||||
|
||||
### Authentication and Identity Management
|
||||
|
||||
To execute operations via the API client, authenticated access is necessary. The most prevalent method for this is obtaining an identity token using a service token:
|
||||
|
||||
```typescript
|
||||
import { CloudlyApiClient } from '@serve.zone/api';
|
||||
|
||||
async function authenticate(client: CloudlyApiClient, serviceToken: string) {
|
||||
const identity = await client.getIdentityByToken(serviceToken, {
|
||||
tagConnection: true,
|
||||
statefullIdentity: true
|
||||
});
|
||||
|
||||
console.log(`Authenticated identity:`, identity);
|
||||
return identity;
|
||||
}
|
||||
|
||||
const serviceToken = 'your_service_token';
|
||||
const identity = await authenticate(cloudlyClient, serviceToken);
|
||||
```
|
||||
|
||||
In this function, the `getIdentityByToken` method authenticates using a service token and acquires an identity object that includes user details and security claims.
|
||||
|
||||
### Interacting with Cloudly Features
|
||||
|
||||
#### Image Management
|
||||
|
||||
Image management is one of the key features supported by the API Client. You can create, upload, and manage Docker images easily within your cloud ecosystem:
|
||||
|
||||
```typescript
|
||||
async function manageImages(client: CloudlyApiClient, identity) {
|
||||
// Creating a new image
|
||||
const newImage = await client.images.createImage({
|
||||
name: 'my_new_image',
|
||||
description: 'A test image'
|
||||
});
|
||||
|
||||
console.log(`Created image:`, newImage);
|
||||
|
||||
// Uploading an image version
|
||||
const imageStream = fetchYourImageStreamHere(); // Provide the source image stream
|
||||
await newImage.pushImageVersion('1.0.0', imageStream);
|
||||
console.log('Image version uploaded successfully.');
|
||||
}
|
||||
|
||||
await manageImages(cloudlyClient, identity);
|
||||
|
||||
// Helper function for obtaining image stream (implement accordingly)
|
||||
function fetchYourImageStreamHere() {
|
||||
// Logic to fetch and return a readable stream for your image
|
||||
return new ReadableStream<Uint8Array>();
|
||||
}
|
||||
```
|
||||
|
||||
In this example, the `manageImages` function underscores the typical workflow of creating an image entry within Cloudly and then proceeding to upload a specific version using the `pushImageVersion` method.
|
||||
|
||||
#### Cluster Configuration
|
||||
|
||||
Another powerful capability is managing clusters, which allows for orchestrating and configuring Docker Swarm clusters:
|
||||
|
||||
```typescript
|
||||
async function configureCluster(client: CloudlyApiClient, identity) {
|
||||
// Fetching cluster configuration
|
||||
const clusterConfig = await client.getClusterConfigFromCloudlyByIdentity(identity);
|
||||
console.log(`Cluster configuration retrieved:`, clusterConfig);
|
||||
}
|
||||
|
||||
await configureCluster(cloudlyClient, identity);
|
||||
```
|
||||
|
||||
The `getClusterConfigFromCloudlyByIdentity` method retrieved the configuration needed to set up and manage your clusters within the multi-cloud environment.
|
||||
|
||||
### Advanced Communication via Typed Sockets
|
||||
|
||||
The API client leverages `TypedRequest` and `TypedSocket` from the `@api.global` family, enabling statically-typed, real-time communication. Here's an example demonstrating socket integration:
|
||||
|
||||
```typescript
|
||||
async function configureSocketCommunication(client: CloudlyApiClient) {
|
||||
client.configUpdateSubject.subscribe({
|
||||
next: (configData) => {
|
||||
console.log('Received configuration update:', configData);
|
||||
}
|
||||
});
|
||||
|
||||
client.serverActionSubject.subscribe({
|
||||
next: (actionRequest) => {
|
||||
console.log('Server action requested:', actionRequest);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
configureSocketCommunication(cloudlyClient);
|
||||
```
|
||||
|
||||
The client utilizes RxJS `Subject` to enable simple yet powerful handling of incoming socket requests, whereby one can act upon updates and actions as they occur.
|
||||
|
||||
### Integrating Certificates
|
||||
|
||||
Certificate operations, such as obtaining SSL certificates for your domains, are also streamlined using this API client:
|
||||
|
||||
```typescript
|
||||
async function retrieveCertificate(client: CloudlyApiClient, domainName: string, identity) {
|
||||
const certificate = await client.getCertificateForDomain({
|
||||
domainName: domainName,
|
||||
type: 'ssl',
|
||||
identity: identity
|
||||
});
|
||||
|
||||
console.log('Retrieved SSL Certificate:', certificate);
|
||||
}
|
||||
|
||||
const yourDomain = 'example.com';
|
||||
await retrieveCertificate(cloudlyClient, yourDomain, identity);
|
||||
```
|
||||
|
||||
This example demonstrates fetching SSL certificates using given domain credentials and an authenticated identity.
|
||||
|
||||
### API Client Cleanup
|
||||
|
||||
When operations are complete and the application is shutting down, it's crucial to gracefully terminate the API client connection:
|
||||
|
||||
```typescript
|
||||
async function cleanup(client: CloudlyApiClient) {
|
||||
await client.stop();
|
||||
console.log('Cloudly API client disconnected gracefully.');
|
||||
}
|
||||
|
||||
await cleanup(cloudlyClient);
|
||||
```
|
||||
|
||||
By invoking the `stop` method, the API client securely terminates its connection to ensure no resources are left hanging, preventing potential memory leaks.
|
||||
|
||||
### Miscellaneous Features
|
||||
|
||||
This section would be remiss without mentioning various utility functionalities such as secret management, server actions, DNS configurator options, and more, all underpinned by an intelligently designed API, enriching cloud resource interactivity.
|
||||
|
||||
In conclusion, by employing `@serve.zone/api`, developers gain unparalleled access to a multitude of modular functions pertinent to multi-cloud administration, significantly amplifying productivity and management effectiveness across diverse computing environments.
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
||||
|
||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||
|
||||
### Trademarks
|
||||
|
||||
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Registered at District court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
17
ts_apiclient/tspublish.json
Normal file
17
ts_apiclient/tspublish.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"order": 1,
|
||||
"name": "@serve.zone/api",
|
||||
"dependencies": [
|
||||
"@serve.zone/interfaces",
|
||||
"@push.rocks/smartpromise",
|
||||
"@push.rocks/smartrx",
|
||||
"@push.rocks/smartstream",
|
||||
"@api.global/typedrequest",
|
||||
"@api.global/typedsocket",
|
||||
"@tsclass/tsclass"
|
||||
],
|
||||
"registries": [
|
||||
"registry.npmjs.org:public",
|
||||
"verdaccio.lossless.digital:public"
|
||||
]
|
||||
}
|
15
ts_cliclient/classes.cliclient.ts
Normal file
15
ts_cliclient/classes.cliclient.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { CloudlyApiClient } from '@serve.zone/api';
|
||||
|
||||
export class CliClient {
|
||||
public cloudlyApiClient: CloudlyApiClient;
|
||||
|
||||
constructor(cloudlyApiClientArg: CloudlyApiClient) {
|
||||
this.cloudlyApiClient = cloudlyApiClientArg;
|
||||
}
|
||||
|
||||
public async getClusters() {
|
||||
const clusters = await this.cloudlyApiClient.cluster.getClusters();
|
||||
console.log(clusters);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { CliClient } from "./classes.cliclient.js";
|
||||
|
||||
export const runCli = async () => {
|
||||
const cliQenv = new plugins.qenv.Qenv();
|
||||
const apiClient = new plugins.servezoneApi.CloudlyApiClient({
|
||||
registerAs: 'cli',
|
||||
cloudlyUrl: await cliQenv.getEnvVarOnDemand('CLOUDLY_URL'),
|
||||
});
|
||||
const cliClient = new CliClient(apiClient);
|
||||
};
|
17
ts_cliclient/plugins.ts
Normal file
17
ts_cliclient/plugins.ts
Normal file
@ -0,0 +1,17 @@
|
||||
// @serve.zone scope
|
||||
import * as servezoneApi from '@serve.zone/api';
|
||||
import * as servezoneInterfaces from '@serve.zone/interfaces';
|
||||
|
||||
export {
|
||||
servezoneApi,
|
||||
servezoneInterfaces
|
||||
}
|
||||
|
||||
// @push.rocks scope
|
||||
import * as projectinfo from '@push.rocks/projectinfo';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
|
||||
export {
|
||||
projectinfo,
|
||||
qenv,
|
||||
}
|
262
ts_cliclient/readme.md
Normal file
262
ts_cliclient/readme.md
Normal file
@ -0,0 +1,262 @@
|
||||
# @serve.zone/cli
|
||||
|
||||
A comprehensive command-line interface (CLI) tool for managing multi-cloud environments, leveraging the features of the @serve.zone/cloudly platform. This CLI is crafted to facilitate seamless interactions with complex cloud configurations and deployments, utilizing Docker Swarmkit orchestration.
|
||||
|
||||
## Install
|
||||
|
||||
To begin using the `@serve.zone/cli` in your projects, install it via npm by running:
|
||||
|
||||
```bash
|
||||
npm install @serve.zone/cli --save
|
||||
```
|
||||
|
||||
This command will download the package and integrate it into your project's `node_modules` directory, reflecting the dependency in your `package.json`.
|
||||
|
||||
## Usage
|
||||
|
||||
The `@serve.zone/cli` is a powerful command-line tool aimed at developers and system administrators who are managing containerized applications across various cloud platforms. Through this CLI, users can interact with their cloud infrastructure efficiently, enabling and extending `Cloudly’s` capabilities directly from the terminal.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before proceeding to use the `@serve.zone/cli`, ensure your system meets the following prerequisites:
|
||||
- Latest Node.js LTS version installed.
|
||||
- Familiarity with basic command-line operations.
|
||||
- Properly configured cloud service accounts (like Cloudflare, Hetzner), necessary for managing respective services.
|
||||
|
||||
### Setting Up the CLI
|
||||
|
||||
Begin setting up the `Cloudly` instance for CLI usage:
|
||||
```typescript
|
||||
// Import required modules
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
import * as path from 'path';
|
||||
|
||||
// Define the configuration needed for cloud operations
|
||||
const cloudlyConfig = {
|
||||
cfToken: 'your-cloudflare-token',
|
||||
hetznerToken: 'your-hetzner-token',
|
||||
environment: 'production',
|
||||
publicUrl: 'your-public-url',
|
||||
};
|
||||
|
||||
// Instantiate and start the Cloudly instance
|
||||
const cloudlyInstance = new Cloudly(cloudlyConfig);
|
||||
await cloudlyInstance.start();
|
||||
|
||||
// Log the setup information to ensure it’s correct
|
||||
console.log(`Cloudly is set up at ${cloudlyInstance.config.data.publicUrl}`);
|
||||
```
|
||||
|
||||
This snippet initializes a Cloudly instance with necessary environment configuration, setting the groundwork for all subsequent CLI operations.
|
||||
|
||||
### Core Operations with the CLI
|
||||
|
||||
Here's how you leverage various operational commands within the CLI feature:
|
||||
|
||||
#### Managing Clusters
|
||||
|
||||
To create, list, and delete clusters, you’ll require invoking the `Cloudly` class with its cluster management logic:
|
||||
|
||||
```typescript
|
||||
// Module imports
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
|
||||
// Async function for cluster management
|
||||
async function manageCluster() {
|
||||
// Prepare configuration
|
||||
const config = {
|
||||
cfToken: 'YOUR_CLOUDFLARE_TOKEN',
|
||||
hetznerToken: 'YOUR_HETZNER_TOKEN',
|
||||
};
|
||||
|
||||
// Initialize Cloudly
|
||||
const cloudlyInstance = new Cloudly(config);
|
||||
await cloudlyInstance.start();
|
||||
|
||||
// Example: Creating a new cluster
|
||||
const cluster = await cloudlyInstance.clusterManager.createCluster({
|
||||
id: 'example_cluster_id',
|
||||
data: {
|
||||
name: 'example_cluster',
|
||||
servers: [],
|
||||
sshKeys: [],
|
||||
}
|
||||
});
|
||||
|
||||
// Log cluster details
|
||||
console.log('Cluster created:', cluster);
|
||||
}
|
||||
```
|
||||
With the above example, you can dynamically manage cluster configurations, ensuring your application components are effectively orchestrated across cloud environments.
|
||||
|
||||
#### Deploying Services
|
||||
|
||||
Deploying cloud-native services within your clusters can be achieved through the CLI:
|
||||
|
||||
```typescript
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
|
||||
// Function to handle service deployment
|
||||
async function deployService() {
|
||||
const config = {
|
||||
cfToken: 'YOUR_CLOUDFLARE_TOKEN',
|
||||
hetznerToken: 'YOUR_HETZNER_TOKEN',
|
||||
};
|
||||
|
||||
const cloudlyInstance = new Cloudly(config);
|
||||
await cloudlyInstance.start();
|
||||
|
||||
// Deploy a new service to a specified cluster
|
||||
const newService = {
|
||||
id: 'example_service_id',
|
||||
data: {
|
||||
name: 'example_service',
|
||||
imageId: 'example_image_id',
|
||||
imageVersion: '1.0.0',
|
||||
environment: {},
|
||||
ports: { web: 80 }
|
||||
}
|
||||
};
|
||||
|
||||
// Store service into database and deploy
|
||||
console.log('Deploying service:', newService)
|
||||
await cloudlyInstance.serverManager.deployService(newService);
|
||||
}
|
||||
|
||||
deployService();
|
||||
```
|
||||
|
||||
By streamlining your service deployments through CLI, you ensure reproducibility and clarity in development operations.
|
||||
|
||||
#### Managing Certificates
|
||||
|
||||
Ensuring secure connections by managing SSL certificates is essential. The CLI aids in this through Let's Encrypt integration:
|
||||
|
||||
```typescript
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
|
||||
// Function to acquire a certificate
|
||||
async function getCertificate() {
|
||||
const config = {
|
||||
cfToken: 'YOUR_CLOUDFLARE_TOKEN',
|
||||
hetznerToken: 'YOUR_HETZNER_TOKEN',
|
||||
};
|
||||
|
||||
const cloudlyInstance = new Cloudly(config);
|
||||
await cloudlyInstance.start();
|
||||
|
||||
// Fetch certificate using Let's Encrypt
|
||||
const domainName = 'example.com';
|
||||
const cert = await cloudlyInstance.letsencryptConnector.getCertificateForDomain(domainName);
|
||||
console.log(`Obtained certificate for domain ${domainName}:`, cert);
|
||||
}
|
||||
|
||||
getCertificate();
|
||||
```
|
||||
|
||||
This process facilitates the automation of SSL certificates provisioning, ensuring high security in your apps.
|
||||
|
||||
### Automating Tasks with the CLI
|
||||
|
||||
Task scheduling is a feature you can utilize to automate recurring processes. Here’s an example of how `@serve.zone/cli` accomplishes task scheduling:
|
||||
|
||||
```typescript
|
||||
import { TaskBuffer } from '@push.rocks/taskbuffer';
|
||||
|
||||
// Schedule a task to run every day
|
||||
const dailyTask = new TaskBuffer({
|
||||
schedule: '0 0 * * *', // Using cron schedule
|
||||
taskFunction: async () => {
|
||||
console.log('Performing daily backup check...');
|
||||
// Include backup logic here
|
||||
},
|
||||
});
|
||||
|
||||
// Initiate task scheduling
|
||||
dailyTask.start();
|
||||
```
|
||||
|
||||
Scheduled tasks like periodic maintenance, data synchronization, or backups ensure you keep your cloud environment robust and reliable.
|
||||
|
||||
### Integrating Third-Party APIs
|
||||
|
||||
Expand the scope of your applications with API integrations offered via `@serve.zone/cli`:
|
||||
|
||||
```typescript
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
|
||||
// Function to send notifications
|
||||
async function sendNotification() {
|
||||
const cloudlyConfig = {
|
||||
cfToken: 'your-cloudflare-token',
|
||||
hetznerToken: 'your-hetzner-token',
|
||||
};
|
||||
|
||||
const cloudly = new Cloudly(cloudlyConfig);
|
||||
await cloudly.start();
|
||||
|
||||
// Configure and send push notification
|
||||
await cloudly.externalApiManager.sendPushMessage({
|
||||
deviceToken: 'some_device_token',
|
||||
message: 'Hello from Cloudly!',
|
||||
});
|
||||
}
|
||||
|
||||
sendNotification();
|
||||
```
|
||||
|
||||
API integrations via the CLI extend Cloudly’s reach, enabling comprehensive service interconnections.
|
||||
|
||||
### Security and Access Management
|
||||
|
||||
Effective identity management is possible through `@serve.zone/cli`. Manage user roles, token validations, and more:
|
||||
|
||||
```typescript
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
|
||||
// Configuring and verifying identity
|
||||
async function authenticateUser() {
|
||||
const cloudlyConfig = {
|
||||
cfToken: 'your-cloudflare-token',
|
||||
hetznerToken: 'your-hetzner-token',
|
||||
};
|
||||
|
||||
const cloudly = new Cloudly(cloudlyConfig);
|
||||
await cloudly.start();
|
||||
|
||||
// Sample user credentials
|
||||
const userIdentity = {
|
||||
userId: 'unique_user_id',
|
||||
jwt: 'user_jwt_token',
|
||||
};
|
||||
|
||||
// Validate identity
|
||||
const isValid = cloudly.authManager.validateIdentity(userIdentity);
|
||||
console.log(`Is user identity valid? ${isValid}`);
|
||||
}
|
||||
|
||||
authenticateUser();
|
||||
```
|
||||
|
||||
The applications of identity validation streamline operational security and enforce access controls across your systems.
|
||||
|
||||
These examples offer a glimpse into the vast potential of @serve.zone/cli, which combines automation, security, and flexibility for state-of-the-art cloud management. You are encouraged to build upon this documentation to harness Cloudly's full capabilities in your infrastructure and process ecosystems. Let the CLI transform your cloud management experience with precision and adaptability.
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
||||
|
||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||
|
||||
### Trademarks
|
||||
|
||||
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Registered at District court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
15
ts_cliclient/tspublish.json
Normal file
15
ts_cliclient/tspublish.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "@serve.zone/cli",
|
||||
"dependencies": [
|
||||
"@serve.zone/api",
|
||||
"@serve.zone/interfaces",
|
||||
"@push.rocks/projectinfo",
|
||||
"@push.rocks/qenv",
|
||||
"@push.rocks/smartcli"
|
||||
],
|
||||
"registries": [
|
||||
"registry.npmjs.org:public",
|
||||
"verdaccio.lossless.digital:public"
|
||||
],
|
||||
"bin": ["servezone"]
|
||||
}
|
8
ts_interfaces/00_commitinfo_data.ts
Normal file
8
ts_interfaces/00_commitinfo_data.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/interfaces',
|
||||
version: '1.1.2',
|
||||
description: 'interfaces for working with containers'
|
||||
}
|
16
ts_interfaces/data/cloudlyconfig.ts
Normal file
16
ts_interfaces/data/cloudlyconfig.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export interface ICloudlyConfig {
|
||||
cfToken?: string;
|
||||
hetznerToken?: string;
|
||||
environment?: 'production' | 'integration';
|
||||
letsEncryptEmail?: string;
|
||||
letsEncryptPrivateKey?: string;
|
||||
jwtKeypair?: plugins.tsclass.network.IJwtKeypair;
|
||||
mongoDescriptor?: plugins.tsclass.database.IMongoDescriptor;
|
||||
s3Descriptor?: plugins.tsclass.storage.IS3Descriptor;
|
||||
publicUrl?: string;
|
||||
publicPort?: string;
|
||||
sslMode?: 'none' | 'letsencrypt' | 'external';
|
||||
servezoneAdminaccount?: string;
|
||||
}
|
36
ts_interfaces/data/cluster.ts
Normal file
36
ts_interfaces/data/cluster.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import { type IDockerRegistryInfo } from '../data/docker.js';
|
||||
import type { IServer } from './server.js';
|
||||
|
||||
export interface ICluster {
|
||||
id: string;
|
||||
data: {
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* a cluster has a machine user that governs access rights.
|
||||
*/
|
||||
userId: string;
|
||||
|
||||
/**
|
||||
* how can the cluster reach cloudly
|
||||
*/
|
||||
cloudlyUrl?: string;
|
||||
|
||||
/**
|
||||
* what servers are expected to be part of the cluster
|
||||
*/
|
||||
servers: IServer[];
|
||||
|
||||
/**
|
||||
* ACME info. This is used to get SSL certificates.
|
||||
*/
|
||||
acmeInfo: {
|
||||
serverAddress: string;
|
||||
serverSecret: string;
|
||||
};
|
||||
|
||||
sshKeys: plugins.tsclass.network.ISshKey[];
|
||||
};
|
||||
}
|
1
ts_interfaces/data/config.ts
Normal file
1
ts_interfaces/data/config.ts
Normal file
@ -0,0 +1 @@
|
||||
export type TConfigType = 'server' | 'cluster' | 'coreflow' | 'service';
|
13
ts_interfaces/data/deployment.ts
Normal file
13
ts_interfaces/data/deployment.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
/**
|
||||
* a deployment happens when a service is deployed
|
||||
* tracks the status of a deployment
|
||||
*/
|
||||
export interface IDeployment {
|
||||
id: string;
|
||||
affectedServiceIds: string[];
|
||||
usedImageId: string;
|
||||
deploymentLog: string[];
|
||||
status: 'scheduled' | 'running' | 'deployed' | 'failed';
|
||||
}
|
15
ts_interfaces/data/docker.ts
Normal file
15
ts_interfaces/data/docker.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export interface IDockerRegistryInfo {
|
||||
serveraddress: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface IServiceRessources {
|
||||
cpuLimit?: number;
|
||||
cpuReservation?: number;
|
||||
memorySizeLimitMB?: number;
|
||||
memorySizeReservationMB?: number;
|
||||
volumeMounts?: plugins.tsclass.container.IVolumeMount[];
|
||||
}
|
11
ts_interfaces/data/event.ts
Normal file
11
ts_interfaces/data/event.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export interface IEvent_Cloudly_ContainerVersionNotification
|
||||
extends plugins.typedrequestInterfaces.implementsTR<
|
||||
plugins.typedrequestInterfaces.ITypedEvent<plugins.tsclass.container.IContainer>,
|
||||
IEvent_Cloudly_ContainerVersionNotification
|
||||
> {
|
||||
name: 'newContainerVersion';
|
||||
uniqueEventId: string;
|
||||
payload: plugins.tsclass.container.IContainer;
|
||||
}
|
12
ts_interfaces/data/externalregistry.ts
Normal file
12
ts_interfaces/data/externalregistry.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export interface IExternalRegistry {
|
||||
id: string;
|
||||
data: {
|
||||
type: 'docker' | 'npm';
|
||||
name: string;
|
||||
url: string;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
}
|
20
ts_interfaces/data/image.ts
Normal file
20
ts_interfaces/data/image.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export interface IImage {
|
||||
id: string;
|
||||
data: {
|
||||
name: string;
|
||||
location: {
|
||||
internal: boolean;
|
||||
externalRegistryId: string;
|
||||
externalImageTag: string;
|
||||
}
|
||||
description: string;
|
||||
versions: Array<{
|
||||
versionString: string;
|
||||
storagePath?: string;
|
||||
size: number;
|
||||
createdAt: number;
|
||||
}>;
|
||||
};
|
||||
}
|
16
ts_interfaces/data/index.ts
Normal file
16
ts_interfaces/data/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export * from './cloudlyconfig.js';
|
||||
export * from './cluster.js';
|
||||
export * from './config.js';
|
||||
export * from './deployment.js';
|
||||
export * from './docker.js';
|
||||
export * from './event.js';
|
||||
export * from './externalregistry.js';
|
||||
export * from './image.js';
|
||||
export * from './secretbundle.js';
|
||||
export * from './secretgroup.js'
|
||||
export * from './server.js';
|
||||
export * from './service.js';
|
||||
export * from './status.js';
|
||||
export * from './traffic.js';
|
||||
export * from './user.js';
|
||||
export * from './version.js';
|
61
ts_interfaces/data/secretbundle.ts
Normal file
61
ts_interfaces/data/secretbundle.ts
Normal file
@ -0,0 +1,61 @@
|
||||
export interface ISecretBundle {
|
||||
id: string;
|
||||
data: {
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* determines if the secret is a service or an external secret
|
||||
* if external secret additional checks are put in place to protect the secret
|
||||
*
|
||||
* * service:
|
||||
* the bundle belongs to a service and can only be used by that service
|
||||
* * npmci:
|
||||
* the bundle is a secret bundle that is used by an npmci pipeline
|
||||
* production secrets will be omitted in any case
|
||||
* * gitzone:
|
||||
* the bundle is a secret bundle that is used by a gitzone.
|
||||
* Only local environment variables are allowed
|
||||
* * external:
|
||||
* the bundle is a secret bundle that is used by an external service
|
||||
*/
|
||||
type: 'service' | 'npmci' | 'gitzone' | 'external';
|
||||
|
||||
|
||||
/**
|
||||
* set this if the secretBundle belongs to a service
|
||||
*/
|
||||
serviceId?: string;
|
||||
|
||||
/**
|
||||
* You can add specific secret groups using this
|
||||
*/
|
||||
includedSecretGroupIds: string[];
|
||||
|
||||
/**
|
||||
* access to this secretBundle also grants access to resources with matching tags
|
||||
*/
|
||||
includedTags: {
|
||||
key: string;
|
||||
value?: string;
|
||||
}[];
|
||||
|
||||
/**
|
||||
* access to this secretBundle also grants access to the images
|
||||
*/
|
||||
imageClaims: {
|
||||
imageId: string;
|
||||
permissions: ('read' | 'write')[];
|
||||
}[];
|
||||
|
||||
/**
|
||||
* authrozations select a specific environment of a config bundle
|
||||
*/
|
||||
authorizations: Array<ISecretBundleAuthorization>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ISecretBundleAuthorization {
|
||||
secretAccessKey: string;
|
||||
environment: string;
|
||||
}
|
54
ts_interfaces/data/secretgroup.ts
Normal file
54
ts_interfaces/data/secretgroup.ts
Normal file
@ -0,0 +1,54 @@
|
||||
export interface ISecretGroup {
|
||||
/**
|
||||
* the insatnce id. This should be a random id, except for default
|
||||
*/
|
||||
id: string;
|
||||
|
||||
data: {
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* the key of the secretgroup like CI_RUNNER_TOKEN
|
||||
*/
|
||||
key: string;
|
||||
|
||||
/**
|
||||
* the priority of the secretgroup
|
||||
* will be used to determine which secretgroup will be used
|
||||
* when there are multiple secretgroups with the same key
|
||||
*/
|
||||
priority?: number;
|
||||
|
||||
/**
|
||||
* any tags that can be used to filter the secretgroup
|
||||
* can be used for putting secrets into projects
|
||||
*/
|
||||
tags: {
|
||||
key: string;
|
||||
value: string;
|
||||
}[];
|
||||
/**
|
||||
* the values for this secretGroup
|
||||
*/
|
||||
environments: {
|
||||
[key: string]: {
|
||||
value: string;
|
||||
|
||||
/**
|
||||
* can be used to update the value
|
||||
*/
|
||||
updateToken?: string;
|
||||
|
||||
/**
|
||||
* the linux timestamp of the last update
|
||||
*/
|
||||
lastUpdated: number;
|
||||
history: {
|
||||
timestamp: string;
|
||||
value: string;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
36
ts_interfaces/data/server.ts
Normal file
36
ts_interfaces/data/server.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import { type IDockerRegistryInfo } from './docker.js';
|
||||
|
||||
export interface IServerMetrics {
|
||||
serverId: string;
|
||||
cpuUsageInPercent: number;
|
||||
memoryUsageinMB: number;
|
||||
memoryAvailableInMB: number;
|
||||
containerCount: number;
|
||||
containerMetrics: Array<{
|
||||
containerId: string;
|
||||
containerName: string;
|
||||
cpuUsageInPercent: number;
|
||||
memoryUsageInMB: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface IServer {
|
||||
id: string;
|
||||
data: {
|
||||
type: 'baremetal' | 'hetzner';
|
||||
|
||||
assignedClusterId: string;
|
||||
|
||||
/**
|
||||
* a list of debian packages to be installed
|
||||
*/
|
||||
requiredDebianPackages: string[];
|
||||
|
||||
/**
|
||||
* a list of SSH keys to deploy
|
||||
*/
|
||||
sshKeys: plugins.tsclass.network.ISshKey[];
|
||||
};
|
||||
}
|
34
ts_interfaces/data/service.ts
Normal file
34
ts_interfaces/data/service.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import type { IServiceRessources } from './docker.js';
|
||||
|
||||
export interface IService {
|
||||
id: string;
|
||||
data: {
|
||||
name: string;
|
||||
description: string;
|
||||
imageId: string;
|
||||
imageVersion: string;
|
||||
environment: { [key: string]: string };
|
||||
/**
|
||||
* the main secret bundle id, exclusive to the service
|
||||
*/
|
||||
secretBundleId: string;
|
||||
/**
|
||||
* those secret bundle ids do not belong to the service itself
|
||||
* and thus live past the service lifecycle
|
||||
*/
|
||||
additionalSecretBundleIds?: string[];
|
||||
scaleFactor: number;
|
||||
balancingStrategy: 'round-robin' | 'least-connections';
|
||||
ports: {
|
||||
web: number;
|
||||
custom?: { [domain: string]: string };
|
||||
};
|
||||
resources?: IServiceRessources;
|
||||
domains: {
|
||||
name: string;
|
||||
port?: number;
|
||||
protocol?: 'http' | 'https' | 'ssh';
|
||||
}[];
|
||||
deploymentIds: string[];
|
||||
};
|
||||
}
|
20
ts_interfaces/data/status.ts
Normal file
20
ts_interfaces/data/status.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export interface IClusterStatus {
|
||||
name: string;
|
||||
ip: string;
|
||||
nodesCount: number;
|
||||
containersUnderManagementCount: number;
|
||||
nodeStatusId: string;
|
||||
containerStatusArray: IContainerStatus[];
|
||||
}
|
||||
|
||||
export interface INodeStatus {
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
export interface IContainerStatus {
|
||||
serviceName: string;
|
||||
dockerImageUrl: string;
|
||||
dockerImageVersion: string;
|
||||
}
|
5
ts_interfaces/data/traffic.ts
Normal file
5
ts_interfaces/data/traffic.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
interface IReverseProxyConfig extends plugins.tsclass.network.IReverseProxyConfig {}
|
||||
|
||||
export { type IReverseProxyConfig };
|
30
ts_interfaces/data/user.ts
Normal file
30
ts_interfaces/data/user.ts
Normal file
@ -0,0 +1,30 @@
|
||||
export interface IToken {
|
||||
token: string;
|
||||
expiresAt: number;
|
||||
assignedRoles: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* an identity is assumed by authentication as a user
|
||||
* an identity is ephemeral and has to be renewed regularly
|
||||
*/
|
||||
export interface IIdentity {
|
||||
name: string;
|
||||
userId: string;
|
||||
type: 'machine' | 'human';
|
||||
role: 'admin' | 'user' | 'api' | 'cluster';
|
||||
expiresAt: number;
|
||||
/** the jwt token should contain above data for verification */
|
||||
jwt: string;
|
||||
}
|
||||
|
||||
export interface IUser {
|
||||
id: string;
|
||||
data: {
|
||||
type: 'machine' | 'human';
|
||||
role: 'admin' | 'user' | 'api' | 'cluster';
|
||||
username?: string;
|
||||
password?: string;
|
||||
tokens?: IToken[];
|
||||
}
|
||||
}
|
11
ts_interfaces/data/version.ts
Normal file
11
ts_interfaces/data/version.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export interface IContainerVersionData {
|
||||
/**
|
||||
* the docker image url
|
||||
* example: registry.gitlab.com/hosttoday/ht-docker-node:latest
|
||||
*/
|
||||
dockerImageUrl: string;
|
||||
/**
|
||||
* the docker image version. Note: This is different from docker tags that are often used for versions.
|
||||
*/
|
||||
dockerImageVersion: string;
|
||||
}
|
9
ts_interfaces/index.ts
Normal file
9
ts_interfaces/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import * as data from './data/index.js';
|
||||
import * as platformservice from './platformservice/index.js';
|
||||
import * as requests from './requests/index.js';
|
||||
|
||||
export {
|
||||
data,
|
||||
platformservice,
|
||||
requests
|
||||
}
|
1
ts_interfaces/platformservice/00readme.md
Normal file
1
ts_interfaces/platformservice/00readme.md
Normal file
@ -0,0 +1 @@
|
||||
The platform folder contains types that can be used for talking with the underlying platform by apps running on serve.zone.
|
23
ts_interfaces/platformservice/aibridge.ts
Normal file
23
ts_interfaces/platformservice/aibridge.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export interface IChat {
|
||||
systemMessage: string;
|
||||
messages: {
|
||||
role: 'assistant' | 'user';
|
||||
content: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface IReq_Chat extends plugins.typedrequestInterfaces.implementsTR<
|
||||
plugins.typedrequestInterfaces.ITypedRequest,
|
||||
IReq_Chat
|
||||
> {
|
||||
method: 'chat',
|
||||
request: {
|
||||
chat: IChat;
|
||||
};
|
||||
response: {
|
||||
chat: IChat;
|
||||
latestMessage: string;
|
||||
}
|
||||
}
|
13
ts_interfaces/platformservice/index.ts
Normal file
13
ts_interfaces/platformservice/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as aibridge from './aibridge.js';
|
||||
import * as letter from './letter.js';
|
||||
import * as mta from './mta.js';
|
||||
import * as pushnotification from './pushnotification.js';
|
||||
import * as sms from './sms.js';
|
||||
|
||||
export {
|
||||
aibridge,
|
||||
letter,
|
||||
mta,
|
||||
pushnotification,
|
||||
sms,
|
||||
}
|
34
ts_interfaces/platformservice/letter.ts
Normal file
34
ts_interfaces/platformservice/letter.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export interface IRequest_SendLetter extends plugins.typedrequestInterfaces.implementsTR<
|
||||
plugins.typedrequestInterfaces.ITypedRequest,
|
||||
IRequest_SendLetter
|
||||
> {
|
||||
method: 'sendLetter';
|
||||
request: {
|
||||
/**
|
||||
* will be used in logs
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* if you send any PDF / invoice that you have not made sure to be letterxpress compliant
|
||||
* we strongly recommend using a cover page
|
||||
*/
|
||||
needsCover: boolean;
|
||||
title?: string;
|
||||
from?: plugins.tsclass.business.IAddress;
|
||||
to?: plugins.tsclass.business.IAddress;
|
||||
coverBody?: string;
|
||||
service: ('Einschreiben')[];
|
||||
pdfAttachments?: Array<{
|
||||
name: string;
|
||||
binaryAttachmentString: string;
|
||||
}>
|
||||
};
|
||||
response: {
|
||||
/**
|
||||
* this process id allows status retrieval of the letter
|
||||
*/
|
||||
processId: string;
|
||||
};
|
||||
}
|
70
ts_interfaces/platformservice/mta.ts
Normal file
70
ts_interfaces/platformservice/mta.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export type TTemplates = 'default' | 'linkaction' | 'notification';
|
||||
|
||||
export interface IReq_SendEmail extends plugins.typedrequestInterfaces.implementsTR<
|
||||
plugins.typedrequestInterfaces.ITypedRequest,
|
||||
IReq_SendEmail
|
||||
> {
|
||||
method: 'sendEmail';
|
||||
request: {
|
||||
title: string;
|
||||
from: string;
|
||||
to: string;
|
||||
body: string;
|
||||
attachments?: Array<{
|
||||
name: string;
|
||||
binaryAttachmentString: string;
|
||||
}>
|
||||
};
|
||||
response: {
|
||||
/**
|
||||
* the response id allows for handling of responses to that email
|
||||
*/
|
||||
responseId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_RegisterRecipient extends plugins.typedrequestInterfaces.implementsTR<
|
||||
plugins.typedrequestInterfaces.ITypedRequest,
|
||||
IReq_RegisterRecipient
|
||||
> {
|
||||
method: 'registerRecepient';
|
||||
request: {
|
||||
emailAddress: string;
|
||||
};
|
||||
response: {
|
||||
status: 'ok' | 'not ok';
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_CheckEmailStatus extends plugins.typedrequestInterfaces.implementsTR<
|
||||
plugins.typedrequestInterfaces.ITypedRequest,
|
||||
IReq_CheckEmailStatus
|
||||
> {
|
||||
method: 'checkEmailStatus';
|
||||
request: {
|
||||
emailId: string;
|
||||
};
|
||||
response: {
|
||||
status: string,
|
||||
details?: { message: string; }
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReq_GetEMailStats extends plugins.typedrequestInterfaces.implementsTR<
|
||||
plugins.typedrequestInterfaces.ITypedRequest,
|
||||
IReq_GetEMailStats
|
||||
> {
|
||||
method: 'getEmailStats';
|
||||
request: {
|
||||
jwt: string;
|
||||
};
|
||||
response: {
|
||||
totalEmailsSent: number;
|
||||
totalEmailsDelivered: number;
|
||||
totalEmailsBounced: number;
|
||||
averageDeliveryTimeMs: number;
|
||||
lastUpdated: string;
|
||||
};
|
||||
}
|
16
ts_interfaces/platformservice/pushnotification.ts
Normal file
16
ts_interfaces/platformservice/pushnotification.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export interface IRequest_SendPushNotification extends plugins.typedrequestInterfaces.implementsTR<
|
||||
plugins.typedrequestInterfaces.ITypedRequest,
|
||||
IRequest_SendPushNotification
|
||||
> {
|
||||
method: 'sendPushNotification';
|
||||
request: {
|
||||
deviceToken: string;
|
||||
message: string;
|
||||
}
|
||||
response: {
|
||||
ok: boolean;
|
||||
status: string;
|
||||
}
|
||||
}
|
33
ts_interfaces/platformservice/sms.ts
Normal file
33
ts_interfaces/platformservice/sms.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export interface IRequest_SendSms
|
||||
extends plugins.typedrequestInterfaces.implementsTR<
|
||||
plugins.typedrequestInterfaces.ITypedRequest,
|
||||
IRequest_SendSms
|
||||
> {
|
||||
method: 'sendSms';
|
||||
request: {
|
||||
toNumber: number;
|
||||
fromName: string;
|
||||
messageText: string;
|
||||
};
|
||||
response: {
|
||||
status: 'ok' | 'not ok';
|
||||
}
|
||||
}
|
||||
|
||||
export interface IRequest_SendVerificationCode
|
||||
extends plugins.typedrequestInterfaces.implementsTR<
|
||||
plugins.typedrequestInterfaces.ITypedRequest,
|
||||
IRequest_SendVerificationCode
|
||||
> {
|
||||
method: 'sendVerificationCode';
|
||||
request: {
|
||||
toNumber: number;
|
||||
fromName: string;
|
||||
};
|
||||
response: {
|
||||
status: 'ok' | 'not ok';
|
||||
verificationCode: string;
|
||||
}
|
||||
}
|
20
ts_interfaces/plugins.ts
Normal file
20
ts_interfaces/plugins.ts
Normal file
@ -0,0 +1,20 @@
|
||||
// @apiglobal scope
|
||||
import * as typedrequestInterfaces from '@api.global/typedrequest-interfaces';
|
||||
|
||||
export {
|
||||
typedrequestInterfaces
|
||||
}
|
||||
|
||||
// @push.rocks scope
|
||||
import * as smartlogInterfaces from '@push.rocks/smartlog-interfaces';
|
||||
|
||||
export {
|
||||
smartlogInterfaces,
|
||||
}
|
||||
|
||||
// tsclass scope
|
||||
import * as tsclass from '@tsclass/tsclass';
|
||||
|
||||
export {
|
||||
tsclass
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user