Compare commits

..

75 Commits

Author SHA1 Message Date
330797ab1a 5.0.6
Some checks failed
Docker (tags) / security (push) Successful in 41s
Docker (tags) / test (push) Failing after 1m57s
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2025-08-18 21:24:47 +00:00
4b3b91312b fix(connector.letsencrypt): Improve Lets Encrypt integration and certificate handling; fix coreflow certificate response; add local assistant permissions config 2025-08-18 21:24:46 +00:00
1580bb1585 5.0.5
Some checks failed
Docker (tags) / security (push) Successful in 1m13s
Docker (tags) / test (push) Failing after 7m12s
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2025-08-18 21:11:28 +00:00
af7fcf6c2e fix(coreflow): Fix Coreflow identity lookup and response shape; improve API client tests and bump dependencies 2025-08-18 21:11:28 +00:00
23c9e3f678 Enhance README for @serve.zone/interfaces: improve structure, add features, and clarify installation instructions 2025-08-18 03:16:28 +00:00
7d4e766e9e Enhance documentation for @serve.zone/api and @serve.zone/cli
- Updated the README for @serve.zone/api to improve clarity and organization, adding sections for features, quick start, authentication, core operations, and advanced usage.
- Improved the installation instructions and added examples for various operations including image management, cluster operations, and real-time updates.
- Enhanced the @serve.zone/cli README with a focus on features, installation, quick start, core commands, and advanced usage.
- Added detailed command examples for cluster management, service deployment, secret management, and DNS management.
- Included sections for CI/CD integration and troubleshooting in both README files.
- Improved formatting and added emojis for better readability and engagement.
2025-08-18 03:14:49 +00:00
907f3e8320 Enhance Cloudly Configuration and Testing Setup
- Updated README to include architecture overview and details on components.
- Changed import paths in test helpers and test files to use the new Git zone packages.
- Modified S3 bucket name in test setup for consistency.
- Updated CloudlyConfig class to use more descriptive environment variable names for MongoDB and S3 configuration.
- Adjusted ImageManager to retrieve the S3 bucket name from the configuration instead of hardcoding it.
2025-08-18 03:07:12 +00:00
bc7a2ca5f1 5.0.4
Some checks failed
Docker (tags) / security (push) Successful in 48s
Docker (tags) / test (push) Successful in 1m56s
Docker (tags) / metadata (push) Successful in 3s
Docker (tags) / release (push) Failing after 14s
2025-04-25 18:20:18 +00:00
77d911e47a fix(platformservice/mta): Update getEmailStatus response schema: make details property optional 2025-04-25 18:20:18 +00:00
b9c9c2d0a9 5.0.3
Some checks failed
Docker (tags) / security (push) Successful in 49s
Docker (tags) / test (push) Successful in 1m55s
Docker (tags) / metadata (push) Successful in 3s
Docker (tags) / release (push) Failing after 14s
2025-04-25 17:02:48 +00:00
d5b91789d1 fix(mta): update email status response type in MTA platform service 2025-04-25 17:02:48 +00:00
eb8350f453 5.0.2
Some checks failed
Docker (tags) / security (push) Successful in 38s
Docker (tags) / test (push) Successful in 1m56s
Docker (tags) / metadata (push) Successful in 3s
Docker (tags) / release (push) Failing after 13s
2025-04-25 16:34:01 +00:00
b987ce27b8 fix(platformservice/mta): Refactor email status response in MTA service 2025-04-25 16:34:00 +00:00
630e363e53 5.0.1
Some checks failed
Docker (tags) / security (push) Successful in 48s
Docker (tags) / test (push) Successful in 1m54s
Docker (tags) / metadata (push) Successful in 3s
Docker (tags) / release (push) Failing after 14s
2025-04-25 16:27:48 +00:00
a602021952 fix(mta): Update email stats response interface in mta platform service to include totalEmailsSent, totalEmailsDelivered, totalEmailsBounced, averageDeliveryTimeMs, and lastUpdated timestamp. 2025-04-25 16:27:48 +00:00
80585437a0 5.0.0
Some checks failed
Docker (tags) / security (push) Successful in 47s
Docker (tags) / test (push) Successful in 1m54s
Docker (tags) / metadata (push) Successful in 3s
Docker (tags) / release (push) Failing after 41s
2025-04-25 15:57:35 +00:00
4674a20a2c BREAKING CHANGE(ts_interfaces/platformservice/mta): Rename mta interfaces and upgrade dependency versions 2025-04-25 15:57:35 +00:00
820cdfcd48 4.13.0
Some checks failed
Docker (tags) / security (push) Successful in 52s
Docker (tags) / test (push) Successful in 3m5s
Docker (tags) / metadata (push) Successful in 8s
Docker (tags) / release (push) Failing after 22s
2025-01-20 02:18:58 +01:00
6e5dd9b05a feat(service): Add support for service creation, update, and deletion. 2025-01-20 02:18:58 +01:00
f3d5c21fab 4.12.2
Some checks failed
Docker (tags) / security (push) Successful in 1m6s
Docker (tags) / test (push) Successful in 3m5s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 24s
2025-01-20 02:11:12 +01:00
04b278ee28 fix(service): Fix secret bundle and service management bugs 2025-01-20 02:11:12 +01:00
7084d76c43 4.12.1
Some checks failed
Docker (tags) / security (push) Successful in 51s
Docker (tags) / test (push) Successful in 2m53s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 20s
2025-01-02 04:07:43 +01:00
41d7550e89 fix(deps): Updated @git.zone/tspublish to version ^1.9.1 2025-01-02 04:07:43 +01:00
4bf361d3a6 4.12.0
Some checks failed
Docker (tags) / security (push) Successful in 1m1s
Docker (tags) / test (push) Successful in 2m57s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 31s
2025-01-02 03:58:09 +01:00
d70617a90c feat(cli): Add CLI support and external registries view 2025-01-02 03:58:09 +01:00
62ad1655d5 4.11.0
Some checks failed
Docker (tags) / security (push) Successful in 1m7s
Docker (tags) / test (push) Successful in 3m0s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 23s
2024-12-30 00:01:27 +01:00
caf3a095f2 feat(external-registry): Introduce external registry management 2024-12-30 00:01:26 +01:00
89e44b2e5f 4.10.0
Some checks failed
Docker (tags) / security (push) Successful in 1m3s
Docker (tags) / test (push) Successful in 2m59s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 22s
2024-12-29 13:40:51 +01:00
a617f51b19 feat(apiclient): Added support for managing external registries in the API client. 2024-12-29 13:40:51 +01:00
355e04fd1d 4.9.0
Some checks failed
Docker (tags) / security (push) Successful in 1m6s
Docker (tags) / test (push) Successful in 3m2s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 30s
2024-12-29 13:19:46 +01:00
89bd767bea feat(apiclient): Add external registry management capabilities to Cloudly API client. 2024-12-29 13:19:46 +01:00
e567ebbf21 4.8.1
Some checks failed
Docker (tags) / security (push) Successful in 53s
Docker (tags) / test (push) Successful in 2m58s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 22s
2024-12-28 21:48:57 +01:00
33311348e2 fix(interfaces): Fix image location schema in IImage interface 2024-12-28 21:48:57 +01:00
d6e914edab 4.8.0
Some checks failed
Docker (tags) / security (push) Successful in 1m5s
Docker (tags) / test (push) Successful in 2m58s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 22s
2024-12-28 21:39:44 +01:00
da7b866f23 feat(manager.registry): Add external registry management 2024-12-28 21:39:44 +01:00
7654d780b1 4.7.1
Some checks failed
Docker (tags) / security (push) Successful in 1m10s
Docker (tags) / test (push) Successful in 3m1s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 23s
2024-12-28 19:50:30 +01:00
dbd9b661c6 fix(secretmanagement): Refactor secret bundle actions and improve authorization handling 2024-12-28 19:50:29 +01:00
e19d0b4deb 4.7.0
Some checks failed
Docker (tags) / security (push) Successful in 53s
Docker (tags) / test (push) Successful in 3m3s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 22s
2024-12-22 20:09:23 +01:00
f0ebb719f7 feat(apiclient): Add method to flatten secret bundles into key-value objects. 2024-12-22 20:09:23 +01:00
c8e0666bc6 4.6.0
Some checks failed
Docker (tags) / security (push) Successful in 54s
Docker (tags) / test (push) Successful in 3m0s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 23s
2024-12-22 20:03:11 +01:00
0d0b106f90 feat(cloudlyapiclient): Extend CloudlyApiClient with cluster, secretbundle, and secretgroup methods 2024-12-22 20:03:11 +01:00
c9073df7cd 4.5.5
Some checks failed
Docker (tags) / security (push) Successful in 1m4s
Docker (tags) / test (push) Successful in 3m0s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 23s
2024-12-22 19:55:56 +01:00
f65200703d fix(apiclient): Fixed image creation method in cloudlyApiClient 2024-12-22 19:55:56 +01:00
57970b3d10 4.5.4
Some checks failed
Docker (tags) / security (push) Successful in 1m6s
Docker (tags) / test (push) Successful in 2m58s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 23s
2024-12-21 22:14:45 +01:00
b4d9f40c41 fix(ts_web): Fix action type and data fields in appstate for CRUD operations 2024-12-21 22:14:45 +01:00
a219725ff6 4.5.3
Some checks failed
Docker (tags) / security (push) Successful in 1m6s
Docker (tags) / test (push) Successful in 2m58s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 23m54s
2024-12-21 20:21:54 +01:00
4b993fc6b3 fix(secret-management): Refactor secret management to use distinct secret bundle and group APIs. Introduce API client classes for secret bundles and groups. 2024-12-21 20:21:54 +01:00
d453da709f 4.5.2
Some checks failed
Docker (tags) / security (push) Successful in 1m4s
Docker (tags) / test (push) Successful in 2m58s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 23m21s
2024-12-20 02:13:50 +01:00
50fac41c95 fix(apiclient): Implemented IService interface in Service class and improved secret bundle documentation. 2024-12-20 02:13:50 +01:00
affce1fcd1 4.5.1
Some checks failed
Docker (tags) / security (push) Successful in 1m4s
Docker (tags) / test (push) Successful in 3m4s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 16s
2024-12-17 19:51:11 +01:00
df67ebd27a fix(core): Updated dependencies in package.json to latest versions. 2024-12-17 19:51:10 +01:00
ef5bfd435a 4.5.0
Some checks failed
Docker (tags) / security (push) Successful in 1m8s
Docker (tags) / test (push) Successful in 3m5s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 23m36s
2024-12-14 20:32:17 +01:00
db07db930c feat(services): Add service management functionalities 2024-12-14 20:32:17 +01:00
f6309f600c 4.4.0
All checks were successful
Docker (tags) / security (push) Successful in 48s
Docker (tags) / test (push) Successful in 2m42s
Docker (tags) / metadata (push) Successful in 6s
Docker (tags) / release (push) Successful in 4m55s
2024-11-18 19:52:15 +01:00
7477704905 feat(api-client): Add static method getImageById for Image class in api-client 2024-11-18 19:52:15 +01:00
db89d86242 4.3.21
All checks were successful
Docker (tags) / security (push) Successful in 58s
Docker (tags) / test (push) Successful in 2m43s
Docker (tags) / metadata (push) Successful in 6s
Docker (tags) / release (push) Successful in 5m0s
2024-11-18 19:43:52 +01:00
b74ce05845 fix(interfaces): Remove deprecated deployment directive and update related interfaces 2024-11-18 19:43:52 +01:00
79db68a4a2 4.3.20
All checks were successful
Docker (tags) / security (push) Successful in 58s
Docker (tags) / test (push) Successful in 2m42s
Docker (tags) / metadata (push) Successful in 6s
Docker (tags) / release (push) Successful in 6m19s
2024-11-18 17:48:26 +01:00
5a3ddcf39b fix(apiclient): Ensure mandatory parameter in CloudlyApiClient constructor 2024-11-18 17:48:26 +01:00
fe6bfc0a83 4.3.19
All checks were successful
Docker (tags) / security (push) Successful in 1m1s
Docker (tags) / test (push) Successful in 2m52s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Successful in 5m3s
2024-11-18 13:41:29 +01:00
36a481ecd1 fix(docker): Fix improper Docker push command preventing push to the correct registry. 2024-11-18 13:41:29 +01:00
f7b2e203ed 4.3.18
All checks were successful
Docker (tags) / security (push) Successful in 1m2s
Docker (tags) / test (push) Successful in 2m49s
Docker (tags) / metadata (push) Successful in 6s
Docker (tags) / release (push) Successful in 4m25s
2024-11-17 07:05:41 +01:00
27c98c4e32 fix(docker_tags): Updated Docker configuration to include NPM tokens. 2024-11-17 07:05:41 +01:00
79257908d0 4.3.17
Some checks failed
Docker (tags) / security (push) Successful in 1m2s
Docker (tags) / test (push) Successful in 2m46s
Docker (tags) / metadata (push) Successful in 9s
Docker (tags) / release (push) Failing after 28s
2024-11-17 06:31:22 +01:00
b5ca898eac fix(Dockerfile): Corrected docker base image tag in Dockerfile for alpine compatibility. 2024-11-17 06:31:22 +01:00
53ade28931 4.3.16
Some checks failed
Docker (tags) / security (push) Successful in 50s
Docker (tags) / test (push) Successful in 2m42s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 32s
2024-11-17 01:04:26 +01:00
fff4c7642d fix(infrastructure): Correct Docker image path in Dockerfile for improved build consistency. 2024-11-17 01:04:26 +01:00
dafe6574cc 4.3.15
Some checks failed
Docker (tags) / security (push) Successful in 1m1s
Docker (tags) / test (push) Successful in 2m47s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 28s
2024-11-17 00:00:56 +01:00
b70dad4996 fix(project setup): fixed incorrect configuration in npmextra.json 2024-11-17 00:00:55 +01:00
17b0b50fbd 4.3.14
Some checks failed
Docker (tags) / security (push) Successful in 50s
Docker (tags) / test (push) Successful in 2m50s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 33s
2024-11-16 22:31:17 +01:00
91a0272ab3 fix(docker tags): Comment out unused secret variables in docker_tags.yaml 2024-11-16 22:31:16 +01:00
efd22d4087 4.3.13
Some checks failed
Docker (tags) / security (push) Successful in 51s
Docker (tags) / test (push) Successful in 2m48s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 35s
2024-11-16 16:08:51 +01:00
c9e32540bf fix(package): Updated package dependencies 2024-11-16 16:08:50 +01:00
8344f96983 4.3.12
Some checks failed
Docker (tags) / security (push) Successful in 43s
Docker (tags) / test (push) Successful in 3m11s
Docker (tags) / metadata (push) Successful in 7s
Docker (tags) / release (push) Failing after 34s
2024-11-06 21:26:23 +01:00
3b77089d79 fix(workflow): Fix Docker image path in GitHub action workflow 2024-11-06 21:26:23 +01:00
66 changed files with 13637 additions and 7609 deletions

View File

@@ -10,8 +10,8 @@ env:
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 }}
@@ -75,7 +75,7 @@ jobs:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: code.foss.global/hosttoday/ht-docker-dbase:npmci
image: code.foss.global/host.today/ht-docker-dbase:npmci
steps:
- uses: actions/checkout@v3
@@ -90,8 +90,7 @@ jobs:
npmci docker login
npmci docker build
npmci docker test
# npmci docker push
npmci docker push
npmci docker push code.foss.global
metadata:
needs: test

View File

@@ -1,6 +1,6 @@
# gitzone dockerfile_service
## STAGE 1 // BUILD
FROM code.foss.global/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 code.foss.global/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 code.foss.global/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 code.foss.global/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

View File

@@ -1,5 +1,244 @@
# Changelog
## 2025-08-18 - 5.0.6 - fix(connector.letsencrypt)
Improve Let's Encrypt integration and certificate handling; fix coreflow certificate response; add local assistant permissions config
- Replace ad-hoc setChallenge/removeChallenge hooks with a DNS-01 handler (smartacme.handlers.Dns01Handler) using Cloudflare to manage ACME DNS challenges.
- Add MongoDB-backed certificate manager (smartacme.certmanagers.MongoCertManager) and pass it to SmartAcme as certManager.
- Initialize SmartAcme with certManager and challengeHandlers instead of setChallenge/removeChallenge/mongoDescriptor options.
- Return certificate object directly from coreflow certificate request handler (avoid createSavableObject) to fix the getCertificateForDomain response payload.
- Add .claude/settings.local.json with local assistant/permissions entries to allow specific debugging/automation commands.
- Bump commitinfo versions to 5.0.6 and update changelog.
## 2025-08-18 - 5.0.6 - fix(connector.letsencrypt)
Improve Let's Encrypt integration and certificate handling; add local assistant permissions config
- Replace ad-hoc setChallenge/removeChallenge hooks with a DNS-01 handler using Cloudflare (smartacme.handlers.Dns01Handler) to manage ACME DNS challenges.
- Add MongoDB-backed certificate manager (smartacme.certmanagers.MongoCertManager) and pass it to SmartAcme as certManager.
- Update SmartAcme initialization to use certManager and challengeHandlers instead of setChallenge/removeChallenge/mongoDescriptor options.
- Return certificate object directly from coreflow certificate request handler (avoid createSavableObject), fixing the response payload for getCertificateForDomain.
- Add .claude/settings.local.json with local assistant/permissions entries to allow specific debugging/automation commands.
## 2025-08-18 - 5.0.5 - fix(coreflow)
Fix Coreflow identity lookup and response shape; improve API client tests and bump dependencies
- ts/manager.coreflow/coreflowmanager.ts: Use $elemMatch to correctly query nested user.tokens when resolving identities and validate machine user types.
- ts/manager.coreflow/coreflowmanager.ts: Normalize getClusterConfig response to include services (was deploymentDirectives) and tidy handler signatures.
- test/test.apiclient.ts: Add detailed logging and improved error handling across preTask, client startup, identity retrieval, image creation and image upload to aid debugging and test observability.
- package.json: Update dependency versions (notable bumps): @types/node -> ^22.0.0, @push.rocks/smartacme -> ^8.0.0, @push.rocks/smartdata -> ^5.16.4, @push.rocks/smartexpect -> ^2.5.0, @push.rocks/smartpath -> ^6.0.0, @push.rocks/smartrequest -> ^4.2.2, plus other maintenance bumps.
- Add .claude/settings.local.json to provide local Claude permissions for developer tooling.
## 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

View File

@@ -6,19 +6,17 @@
"dockerRegistryRepoMap": {
"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 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": [
"multi-cloud management",
"Docker Swarmkit",

View File

@@ -1,6 +1,6 @@
{
"name": "@serve.zone/cloudly",
"version": "4.3.11",
"version": "5.0.6",
"private": false,
"description": "A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.",
"type": "module",
@@ -13,7 +13,7 @@
"author": "Task Venture Capital GmbH",
"license": "MIT",
"scripts": {
"test": "(tstest test/)",
"test": "(tstest test/ --verbose --logfile --timeout 120)",
"build": "tsbuild tsfolders --web --allowimplicitany && tsbundle website --production",
"start": "node cli.js",
"startTs": "node cli.ts.js",
@@ -22,59 +22,58 @@
"docs": "tsdoc aidoc"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.2.0",
"@git.zone/tsbundle": "^2.1.0",
"@git.zone/tsdoc": "^1.4.2",
"@git.zone/tspublish": "^1.7.7",
"@git.zone/tstest": "^1.0.90",
"@git.zone/tswatch": "^2.0.25",
"@push.rocks/tapbundle": "^5.5.0",
"@types/node": "^22.9.0"
"@git.zone/tsbuild": "^2.6.7",
"@git.zone/tsbundle": "^2.5.1",
"@git.zone/tsdoc": "^1.5.1",
"@git.zone/tspublish": "^1.10.3",
"@git.zone/tstest": "^2.3.5",
"@git.zone/tswatch": "^2.2.1",
"@types/node": "^22.0.0"
},
"dependencies": {
"@api.global/typedrequest": "3.1.10",
"@api.global/typedrequest-interfaces": "^3.0.19",
"@api.global/typedserver": "^3.0.51",
"@api.global/typedserver": "^3.0.77",
"@api.global/typedsocket": "^3.0.1",
"@apiclient.xyz/cloudflare": "^6.0.1",
"@apiclient.xyz/docker": "^1.2.7",
"@apiclient.xyz/cloudflare": "^6.4.1",
"@apiclient.xyz/docker": "^1.3.0",
"@apiclient.xyz/hetznercloud": "^1.2.0",
"@apiclient.xyz/slack": "^3.0.9",
"@design.estate/dees-catalog": "^1.2.0",
"@design.estate/dees-domtools": "^2.0.64",
"@design.estate/dees-element": "^2.0.39",
"@design.estate/dees-catalog": "^1.10.10",
"@design.estate/dees-domtools": "^2.3.3",
"@design.estate/dees-element": "^2.1.2",
"@git.zone/tsrun": "^1.3.3",
"@push.rocks/early": "^4.0.3",
"@push.rocks/npmextra": "^5.1.2",
"@push.rocks/npmextra": "^5.3.3",
"@push.rocks/projectinfo": "^5.0.1",
"@push.rocks/qenv": "^6.0.5",
"@push.rocks/smartacme": "^5.0.0",
"@push.rocks/smartbucket": "^3.0.23",
"@push.rocks/qenv": "^6.1.3",
"@push.rocks/smartacme": "^8.0.0",
"@push.rocks/smartbucket": "^3.3.10",
"@push.rocks/smartcli": "^4.0.11",
"@push.rocks/smartclickhouse": "^2.0.17",
"@push.rocks/smartdata": "^5.2.10",
"@push.rocks/smartdata": "^5.16.4",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartexit": "^1.0.23",
"@push.rocks/smartexpect": "^1.2.1",
"@push.rocks/smartfile": "^11.0.21",
"@push.rocks/smartexpect": "^2.5.0",
"@push.rocks/smartfile": "^11.2.7",
"@push.rocks/smartguard": "^3.1.0",
"@push.rocks/smartjson": "^5.0.19",
"@push.rocks/smartjwt": "^2.2.1",
"@push.rocks/smartlog": "^3.0.7",
"@push.rocks/smartlog": "^3.1.8",
"@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.4",
"@push.rocks/smartrequest": "^2.0.23",
"@push.rocks/smartrx": "^3.0.7",
"@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrequest": "^4.2.2",
"@push.rocks/smartrx": "^3.0.10",
"@push.rocks/smartssh": "^2.0.1",
"@push.rocks/smartstate": "^2.0.19",
"@push.rocks/smartstream": "^3.2.4",
"@push.rocks/smartstate": "^2.0.26",
"@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",
"@tsclass/tsclass": "^4.1.2"
"@tsclass/tsclass": "^9.2.0"
},
"files": [
"ts/**/*",
@@ -126,5 +125,6 @@
"frontend",
"backend",
"security"
]
],
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
}

17057
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

4
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,4 @@
onlyBuiltDependencies:
- esbuild
- mongodb-memory-server
- puppeteer

View File

@@ -8,4 +8,12 @@
`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.
- For now, cloud wise only the setup with cloudron and hetzner cloud is supported.
## Architecture Overview
- serve.zone is a monorepo containing multiple packages that work together to provide a complete container orchestration platform
- Uses Docker Swarm as the underlying container orchestration technology
- cloudly acts as the control plane providing API, web UI, and CLI interfaces
- coreflow runs inside Docker Swarm clusters to manage containers
- coretraffic runs on each node to handle traffic routing and SSL
- spark manages individual servers at the OS level

461
readme.md
View File

@@ -1,335 +1,242 @@
# @serve.zone/cloudly
# @serve.zone/cloudly 🚀
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.
**Multi-cloud orchestration made simple.** Manage containerized applications across cloud providers with Docker Swarmkit, featuring web dashboards, CLI tools, and powerful APIs.
## Install
## 🎯 What is Cloudly?
To install `@serve.zone/cloudly`, run the following command in your terminal:
Cloudly is your command center for multi-cloud infrastructure. It abstracts away the complexity of managing resources across different cloud providers while giving you the power and flexibility you need for modern DevOps workflows.
### ✨ Key Features
- **🌐 Multi-Cloud Management** - Seamlessly orchestrate resources across Cloudflare, Hetzner, DigitalOcean and more
- **🐳 Docker Swarmkit Integration** - Native container orchestration with production-grade reliability
- **🔐 Secret Management** - Secure handling of credentials, API keys, and sensitive configuration
- **🎨 Web Dashboard** - Beautiful, responsive UI built with modern web components
- **⚡ CLI & API** - Full programmatic control through TypeScript/JavaScript APIs and command-line tools
- **🔒 SSL/TLS Automation** - Automatic certificate provisioning via Let's Encrypt
- **📊 Comprehensive Logging** - Built-in log aggregation and monitoring capabilities
- **🔄 Task Scheduling** - Automated workflows and background job management
## 🚀 Quick Start
### Installation
```bash
npm install @serve.zone/cloudly --save
# Install the main package
pnpm add @serve.zone/cloudly
# Or install the CLI globally
pnpm add -g @serve.zone/cli
# Or just the API client
pnpm add @serve.zone/api
```
This will install the package and add it to your project's `package.json` dependencies.
## Usage
`@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.
### 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. Heres how to get started:
```typescript
import { Cloudly, ICloudlyConfig } from '@serve.zone/cloudly';
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',
},
};
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
```
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.
### 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. Heres an example of how to create a cluster using `Cloudly`:
```typescript
import { Cloudly } from '@serve.zone/cloudly';
interface ICluster {
name: string;
id: string;
cloudlyUrl: string;
servers: string[];
sshKeys: string[];
}
// Initialize Cloudly with your configuration
const cloudly = new Cloudly({
cfToken: process.env.CLOUDFLARE_TOKEN,
hetznerToken: process.env.HETZNER_TOKEN,
environment: 'production',
letsEncryptEmail: 'certs@example.com',
publicUrl: 'cloudly.example.com',
publicPort: 443,
mongoDescriptor: {
mongoDbUrl: process.env.MONGODB_URL,
mongoDbName: 'cloudly',
mongoDbUser: process.env.MONGODB_USER,
mongoDbPass: process.env.MONGODB_PASS,
}
});
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',
},
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 newCluster: ICluster = {
name: 'example_cluster',
id: 'example_cluster_id',
cloudlyUrl: 'https://example.com:8443',
servers: [],
sshKeys: [],
};
// Store the newly created cluster with Cloudly
const storedCluster = await myCloudlyInstance.clusterManager.storeCluster(newCluster);
console.log('Cluster stored:', storedCluster);
}
manageClusters();
// Start the platform
await cloudly.start();
console.log('🎉 Cloudly is running!');
```
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.
## 🏗️ Architecture
#### Integrating With Cloudflare for DNS Management
Cloudly follows a modular architecture with clear separation of concerns:
`@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:
```
┌─────────────────────────────────────────────┐
│ Web Dashboard (UI) │
├─────────────────────────────────────────────┤
│ API Layer (TypedRouter) │
├─────────────────────────────────────────────┤
│ Core Managers │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Cluster │ │ Image │ │ Secret │ ... │
│ └─────────┘ └─────────┘ └─────────┘ │
├─────────────────────────────────────────────┤
│ Cloud Connectors │
│ ┌──────────┐ ┌─────────┐ ┌──────────----┐ │
│ │Cloudflare│ │ Hetzner │ │ DigitalOcean │ │
│ └──────────┘ └─────────┘ └──────────----┘ │
└─────────────────────────────────────────────┘
```
## 💻 Core Components
### 🔧 Managers
- **AuthManager** - Identity and access management
- **ClusterManager** - Docker Swarm cluster orchestration
- **ImageManager** - Container image lifecycle management
- **SecretManager** - Secure credential storage and distribution
- **ServerManager** - Cloud server provisioning and management
- **TaskManager** - Background job scheduling and execution
### 🔌 Connectors
- **CloudflareConnector** - DNS, CDN, and edge services
- **LetsencryptConnector** - Automatic SSL certificate provisioning
- **MongodbConnector** - Database persistence layer
- **HetznerConnector** - German cloud infrastructure
- **DigitalOceanConnector** - Developer-friendly cloud resources
## 📚 Usage Examples
### Managing Clusters
```typescript
import { Cloudly } from '@serve.zone/cloudly';
// Create a new cluster
const cluster = await cloudly.clusterManager.createCluster({
name: 'production-cluster',
region: 'eu-central',
nodeCount: 3
});
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',
},
};
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
await myCloudlyInstance.start();
const cfConnector = myCloudlyInstance.cloudflareConnector.cloudflare;
const dnsRecord = await cfConnector.createDNSRecord('example.com', 'sub.example.com', 'A', '127.0.0.1');
console.log('DNS Record:', dnsRecord);
}
configureCloudflareDNS();
// Deploy a service
await cluster.deployService({
name: 'api-service',
image: 'myapp:latest',
replicas: 3,
ports: [{ published: 80, target: 3000 }]
});
```
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:
### Secret Management
```typescript
import { Cloudly } from '@serve.zone/cloudly';
// Create a secret group
const secretGroup = await cloudly.secretManager.createSecretGroup({
name: 'api-credentials',
secrets: [
{ key: 'API_KEY', value: process.env.API_KEY },
{ key: 'DB_PASSWORD', value: process.env.DB_PASSWORD }
]
});
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',
},
};
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
await myCloudlyInstance.start();
const doConnector = myCloudlyInstance.digitaloceanConnector;
const droplet = await doConnector.createDroplet('example-droplet', 'nyc3', 's-1vcpu-1gb', 'ubuntu-20-04-x64');
console.log('Droplet created:', droplet);
}
createDigitalOceanDroplets();
// Create a bundle for deployment
const bundle = await cloudly.secretManager.createSecretBundle({
name: 'production-secrets',
secretGroups: [secretGroup]
});
```
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.
### DNS Management
### Advanced Use Cases
```typescript
// Create DNS records via Cloudflare
const record = await cloudly.cloudflareConnector.createDNSRecord(
'example.com',
'api.example.com',
'A',
'192.168.1.1'
);
```
#### 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.
### Web Dashboard
```typescript
import { html } from '@design.estate/dees-element';
const renderDashboard = () => {
return html`
<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>
`;
};
// Create a custom dashboard view
const dashboard = html`
<cloudly-dashboard>
<cloudly-view-clusters></cloudly-view-clusters>
<cloudly-view-dns></cloudly-view-dns>
<cloudly-view-images></cloudly-view-images>
</cloudly-dashboard>
`;
document.body.appendChild(renderDashboard());
document.body.appendChild(dashboard);
```
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.
## 🛠️ CLI Usage
#### Comprehensive Log Management
The CLI provides quick access to all Cloudly features:
With Cloudlys Log Management capabilities, you can track and analyze system logs for better insights into your cloud ecosystems behavior:
```bash
# Login to your Cloudly instance
servezone login --url https://cloudly.example.com
```typescript
import { Cloudly } from '@serve.zone/cloudly';
# List clusters
servezone clusters list
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',
},
};
# Deploy a service
servezone deploy --cluster prod --image myapp:latest
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
await myCloudlyInstance.start();
# Manage secrets
servezone secrets create --name api-key --value "secret123"
const logs = await myCloudlyInstance.logManager.fetchLogs();
console.log('Logs:', logs);
}
initiateLogManagement();
# View logs
servezone logs --service api-service --follow
```
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.
## 📦 Package Exports
#### Secret Management and Bundles
This monorepo publishes multiple packages:
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:
- **@serve.zone/cloudly** - Main orchestration platform
- **@serve.zone/api** - TypeScript/JavaScript API client
- **@serve.zone/cli** - Command-line interface
- **@serve.zone/interfaces** - Shared TypeScript interfaces
```typescript
import { Cloudly } from '@serve.zone/cloudly';
## 🔒 Security Features
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',
},
};
- **End-to-end encryption** for secrets
- **Role-based access control** (RBAC)
- **Automatic SSL/TLS** certificate management
- **Secure token-based authentication**
- **Audit logging** for compliance
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
await myCloudlyInstance.start();
## 🚢 Production Ready
const newSecretGroup = await myCloudlyInstance.secretManager.createSecretGroup({
name: 'example_secret_group',
secrets: [
{ key: 'SECRET_KEY', value: 's3cr3t' },
],
});
Cloudly is battle-tested in production environments managing:
- High-traffic web applications
- Microservice architectures
- CI/CD pipelines
- Data processing workloads
- Real-time communication systems
const newSecretBundle = await myCloudlyInstance.secretManager.createSecretBundle({
name: 'example_bundle',
secretGroups: [newSecretGroup],
});
## 🤝 Development
console.log('Created Secret Group and Bundle:', newSecretGroup, newSecretBundle);
}
```bash
# Clone the repository
git clone https://gitlab.com/servezone/private/cloudly.git
createSecrets();
# Install dependencies
pnpm install
# Run tests
pnpm test
# Build the project
pnpm build
# Start development mode
pnpm watch
```
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.
## 📖 Documentation
### 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.
For detailed documentation, API references, and guides, visit our [documentation site](https://cloudly.serve.zone).
## License and Legal Information
@@ -348,4 +255,4 @@ 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.
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.

View File

@@ -5,13 +5,13 @@ import * as cloudly from '../../ts/index.js';
const stopFunctions: Array<() => Promise<void>> = [];
const tapToolsNodeMod = await import('@push.rocks/tapbundle/node');
const tapToolsNodeMod = await import('@git.zone/tstest/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');
await smarts3.createBucket('cloudly_test_bucket');
stopFunctions.push(async () => {
await smarts3.stop();
});
@@ -23,7 +23,9 @@ export const testCloudlyConfig: cloudly.ICloudlyConfig = {
publicUrl: '127.0.0.1',
publicPort: '8080',
mongoDescriptor: await smartmongo.getMongoDescriptor(),
s3Descriptor: await smarts3.getS3Descriptor(),
s3Descriptor: await smarts3.getS3Descriptor({
bucketName: 'cloudly_test_bucket'
}),
sslMode: 'none',
...(() => {
if (process.env.NPMCI_SECRET01) {

View File

@@ -1,4 +1,4 @@
import { tap, expect } from '@push.rocks/tapbundle';
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as helpers from './helpers/index.js';
import * as cloudly from '../ts/index.js';
@@ -14,8 +14,10 @@ tap.preTask('should start cloudly', async () => {
});
tap.preTask('should create a new machine user for testing', async () => {
console.log('🔵 PreTask: Creating first machine user...');
const machineUser = new testCloudly.authManager.CUser();
machineUser.id = await testCloudly.authManager.CUser.getNewId();
console.log(` - User ID: ${machineUser.id}`);
machineUser.data = {
type: 'machine',
username: 'test',
@@ -27,48 +29,103 @@ tap.preTask('should create a new machine user for testing', async () => {
}],
role: 'admin',
};
console.log(` - Username: ${machineUser.data.username}`);
console.log(` - Role: ${machineUser.data.role}`);
console.log(` - Token: 'test'`);
console.log(` - Token roles: ${machineUser.data.tokens[0].assignedRoles}`);
await machineUser.save();
console.log('✅ PreTask: First machine user saved successfully');
});
tap.test('should create a new cloudlyApiClient', async () => {
console.log('🔵 Test: Creating CloudlyApiClient...');
testClient = new cloudlyApiClient.CloudlyApiClient({
registerAs: 'api',
cloudlyUrl: `http://${helpers.testCloudlyConfig.publicUrl}:${helpers.testCloudlyConfig.publicPort}`,
});
console.log(` - URL: http://${helpers.testCloudlyConfig.publicUrl}:${helpers.testCloudlyConfig.publicPort}`);
await testClient.start();
console.log('✅ CloudlyApiClient started successfully');
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('DEBUG: Check existing users', async () => {
console.log('🔍 DEBUG: Checking existing users in database...');
const allUsers = await testCloudly.authManager.CUser.getInstances({});
console.log(` - Total users found: ${allUsers.length}`);
for (const user of allUsers) {
console.log(` - User: ${user.data.username} (ID: ${user.id})`);
console.log(` - Type: ${user.data.type}`);
console.log(` - Role: ${user.data.role}`);
console.log(` - Tokens: ${user.data.tokens.length}`);
for (const token of user.data.tokens) {
console.log(` - Token: '${token.token}' | Roles: ${token.assignedRoles?.join(', ')}`);
}
}
});
tap.test('should get an identity', async () => {
const identity = await testClient.getIdentityByToken('test');
expect(identity).toBeTruthy();
console.log(identity);
console.log('🔵 Test: Getting identity by token...');
console.log(` - Using token: 'test'`);
console.log(` - API URL: http://${helpers.testCloudlyConfig.publicUrl}:${helpers.testCloudlyConfig.publicPort}`);
try {
const identity = await testClient.getIdentityByToken('test');
console.log('✅ Identity retrieved successfully:');
console.log(` - Identity exists: ${!!identity}`);
if (identity) {
console.log(` - Identity data:`, JSON.stringify(identity, null, 2));
}
expect(identity).toBeTruthy();
} catch (error) {
console.error('❌ Failed to get identity:');
console.error(` - Error message: ${error.message}`);
console.error(` - Error stack:`, error.stack);
throw error;
}
});
let image: Image;
tap.test('should create and upload an image', async () => {
image = await testClient.images.createImage({
name: 'test',
description: 'test'
});
console.log('created image: ', image);
expect(image).toBeInstanceOf(Image);
console.log('🔵 Test: Creating and uploading image...');
console.log(` - Image name: 'test'`);
console.log(` - Image description: 'test'`);
try {
image = await testClient.image.createImage({
name: 'test',
description: 'test'
});
console.log('✅ Image created successfully:');
console.log(` - Image ID: ${image?.id}`);
console.log(` - Image data:`, image);
expect(image).toBeInstanceOf(Image);
} catch (error) {
console.error('❌ Failed to create image:');
console.error(` - Error message: ${error.message}`);
console.error(` - Error stack:`, error.stack);
throw error;
}
})
tap.test('should upload an image version', async () => {
const imageStream = await helpers.getAlpineImageReadableStream();
await image.pushImageVersion('v1.0.0', imageStream);
console.log('🔵 Test: Uploading image version...');
console.log(` - Version: 'v1.0.0'`);
console.log(` - Image exists: ${!!image}`);
console.log(` - Image ID: ${image?.id}`);
try {
const imageStream = await helpers.getAlpineImageReadableStream();
console.log(' - Image stream obtained successfully');
await image.pushImageVersion('v1.0.0', imageStream);
console.log('✅ Image version uploaded successfully');
} catch (error) {
console.error('❌ Failed to upload image version:');
console.error(` - Error message: ${error.message}`);
console.error(` - Error stack:`, error.stack);
throw error;
}
});
tap.test('should stop the apiclient', async (toolsArg) => {

View File

@@ -1,4 +1,4 @@
import { expect, tap } from '@push.rocks/tapbundle';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as helpers from './helpers/index.js';
import * as cloudly from '../ts/index.js';

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/cloudly',
version: '4.3.11',
version: '5.0.6',
description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.'
}

View File

@@ -63,7 +63,7 @@ for (let i = 0; i < demoSecretGroups.length; i++) {
id: `configBundleId${i + 1}`,
data: {
name: `Demo Config Bundle ${i + 1}`,
includedImages: [],
imageClaims: [],
type: 'external',
description: 'Demo Purpose',
includedSecretGroupIds: [secretGroup.id],

View File

@@ -20,6 +20,7 @@ import { CloudlyTaskmanager } from './manager.task/taskmanager.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 './logger.js';
import { CloudlyAuthManager } from './manager.auth/classes.authmanager.js';
@@ -54,6 +55,7 @@ export class Cloudly {
public clusterManager: ClusterManager;
public coreflowManager: CloudlyCoreflowManager;
public externalApiManager: ExternalApiManager;
public externalRegistryManager: ExternalRegistryManager;
public imageManager: ImageManager;
public taskManager: CloudlyTaskmanager;
public serverManager: CloudlyServerManager;
@@ -80,6 +82,7 @@ export class Cloudly {
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);

View File

@@ -29,16 +29,17 @@ export class CloudlyConfig {
publicPort: 'SERVEZONE_PORT',
mongoDescriptor: {
mongoDbUrl: 'MONGODB_URL',
mongoDbName: 'MONGODB_DATABASE',
mongoDbName: 'MONGODB_NAME',
mongoDbUser: 'MONGODB_USER',
mongoDbPass: 'MONGODB_PASSWORD',
mongoDbPass: 'MONGODB_PASS',
},
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,
useSsl: 'boolean:S3_USESSL' as any as boolean,
bucketName: 'S3_BUCKET'
},
sslMode:
'SERVEZONE_SSLMODE' as plugins.servezoneInterfaces.data.ICloudlyConfig['sslMode'],

View File

@@ -18,21 +18,22 @@ export class LetsencryptConnector {
* inits letsencrypt
*/
public async init() {
// Create DNS-01 challenge handler using Cloudflare
const dnsHandler = new plugins.smartacme.handlers.Dns01Handler(
this.cloudlyRef.cloudflareConnector.cloudflare
);
// Create MongoDB certificate manager
const certManager = new plugins.smartacme.certmanagers.MongoCertManager(
this.cloudlyRef.config.data.mongoDescriptor
);
this.smartacme = new plugins.smartacme.SmartAcme({
accountEmail: this.cloudlyRef.config.data.letsEncryptEmail,
accountPrivateKey: this.cloudlyRef.config.data.letsEncryptPrivateKey,
environment: this.cloudlyRef.config.data.environment,
setChallenge: async (dnsChallenge) => {
await this.cloudlyRef.cloudflareConnector.cloudflare.convenience.acmeSetDnsChallenge(
dnsChallenge,
);
},
removeChallenge: async (dnsChallenge) => {
await this.cloudlyRef.cloudflareConnector.cloudflare.convenience.acmeRemoveDnsChallenge(
dnsChallenge,
);
},
mongoDescriptor: this.cloudlyRef.config.data.mongoDescriptor,
certManager: certManager,
challengeHandlers: [dnsHandler],
});
await this.smartacme.start().catch((err) => {
console.error('error in init', err);

View File

@@ -38,13 +38,13 @@ 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 {
@@ -56,12 +56,12 @@ export class ClusterManager {
);
// delete cluster
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_DeleteCluster>(
new plugins.typedrequest.TypedHandler('deleteCluster', async (reqDataArg, toolsArg) => {
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 {
success: true,
ok: true,
};
}),
);

View File

@@ -16,24 +16,23 @@ export class CloudlyCoreflowManager {
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken>(
new plugins.typedrequest.TypedHandler('getIdentityByToken', async (requestData) => {
// Use getInstance with $elemMatch for querying nested arrays
const user = await this.cloudlyRef.authManager.CUser.getInstance({
data: {
tokens: [
{
token: requestData.token,
},
], // find the proper user here.
} as any,
tokens: {
$elemMatch: { token: requestData.token },
},
},
});
if (!user) {
throw new plugins.typedrequest.TypedResponseError(
'The supplied token is not valid. No matching user found.',
'The supplied token is not valid. No matching user found.'
);
}
if (user.data.type !== 'machine') {
throw new plugins.typedrequest.TypedResponseError(
'The supplied token is not valid. The user is not a machine.',
'The supplied token is not valid. The user is not a machine.'
);
}
let cluster: Cluster;
@@ -61,7 +60,7 @@ export class CloudlyCoreflowManager {
}),
},
};
}),
})
);
// lets enable the getting of cluster configs
@@ -76,10 +75,10 @@ export class CloudlyCoreflowManager {
console.log('got cluster config and sending it back to coreflow');
return {
configData: await cluster.createSavableObject(),
deploymentDirectives: [],
services: [],
};
},
),
}
)
);
// lets enable getting of certificates
@@ -89,14 +88,14 @@ export class CloudlyCoreflowManager {
async (dataArg) => {
console.log(`incoming API request for certificate ${dataArg.domainName}`);
const cert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain(
dataArg.domainName,
dataArg.domainName
);
console.log(`got certificate ready for reponse ${dataArg.domainName}`);
return {
certificate: await cert.createSavableObject(),
certificate: cert,
};
},
),
}
)
);
}
}

View 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();
}
}

View File

@@ -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(),
};
})
);
}
}

View File

@@ -0,0 +1,2 @@
export * from './classes.externalregistrymanager.js';
export * from './classes.externalregistry.js';

View File

@@ -156,7 +156,7 @@ export class ImageManager {
this.smartbucketInstance = new plugins.smartbucket.SmartBucket(
this.cloudlyRef.config.data.s3Descriptor,
);
const bucket = await this.smartbucketInstance.getBucketByName('cloudly-test');
const bucket = await this.smartbucketInstance.getBucketByName(s3Descriptor.bucketName);
await bucket.fastPut({ path: 'images/00init', contents: 'init' });
this.imageDir = await bucket.getDirectoryFromPath({

View File

@@ -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;
}
}

View File

@@ -35,20 +35,71 @@ export class CloudlySecretManager {
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_GetConfigBundlesAndSecretGroups>(
'adminGetConfigBundlesAndSecretGroups',
// 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()),
)),
],
};
},
),
);
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()),
@@ -59,73 +110,64 @@ export class CloudlySecretManager {
),
);
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,
),
};
},
),

View File

@@ -1,3 +1,4 @@
import { SecretBundle } from 'ts/manager.secret/classes.secretbundle.js';
import * as plugins from '../plugins.js';
import { ServiceManager } from './classes.servicemanager.js';
@@ -6,9 +7,51 @@ export class Service extends plugins.smartdata.SmartDataDbDoc<
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;
}
}

View File

@@ -14,5 +14,87 @@ export class ServiceManager {
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,
};
}
)
);
}
}

View File

@@ -3,6 +3,11 @@ 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;
@@ -20,7 +25,7 @@ export class CloudlyApiClient {
plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction['request']
>();
constructor(optionsArg?: {
constructor(optionsArg: {
registerAs: TClientType;
cloudlyUrl?: string;
}) {
@@ -155,8 +160,24 @@ export class CloudlyApiClient {
return typedResponse.certificate;
}
public images = {
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);
},
@@ -164,4 +185,56 @@ export class CloudlyApiClient {
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);
}
}
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -18,6 +18,19 @@ export class Image implements plugins.servezoneInterfaces.data.IImage {
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
*/

View 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;
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -6,11 +6,13 @@ 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,

View File

@@ -1,186 +1,290 @@
# @serve.zone/api
# @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.
**The powerful API client for Cloudly.** Connect your applications to multi-cloud infrastructure with type-safe, real-time communication.
## Install
## 🎯 What is @serve.zone/api?
To install the `@serve.zone/api` package, execute the following command in your terminal:
This is your programmatic gateway to the Cloudly platform. Built with TypeScript, it provides a robust, type-safe interface for managing cloud resources, orchestrating containers, and automating infrastructure operations.
## ✨ Features
- **🔒 Type-Safe** - Full TypeScript support with comprehensive interfaces
- **⚡ Real-Time** - WebSocket-based communication for instant updates
- **🔑 Secure Authentication** - Token-based identity management
- **📦 Resource Management** - Complete control over clusters, images, and services
- **🎭 Multi-Identity** - Support for service accounts and user authentication
- **🔄 Reactive Streams** - RxJS integration for event-driven programming
## 🚀 Installation
```bash
npm install @serve.zone/api --save
pnpm add @serve.zone/api
```
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:
## 🎬 Quick Start
```typescript
import { CloudlyApiClient } from '@serve.zone/api';
async function authenticate(client: CloudlyApiClient, serviceToken: string) {
const identity = await client.getIdentityByToken(serviceToken, {
tagConnection: true,
statefullIdentity: true
});
// Initialize the client
const client = new CloudlyApiClient({
registerAs: 'api',
cloudlyUrl: 'https://cloudly.example.com:443'
});
console.log(`Authenticated identity:`, identity);
return identity;
}
// Start the connection
await client.start();
const serviceToken = 'your_service_token';
const identity = await authenticate(cloudlyClient, serviceToken);
// Authenticate with a service token
const identity = await client.getIdentityByToken('your-service-token', {
tagConnection: true,
statefullIdentity: true
});
console.log('🎉 Connected as:', identity.name);
```
In this function, the `getIdentityByToken` method authenticates using a service token and acquires an identity object that includes user details and security claims.
## 🔐 Authentication
### 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:
### Service Token Authentication
```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>();
}
// Authenticate using a service token
const identity = await client.getIdentityByToken(serviceToken, {
tagConnection: true, // Tag this connection with the identity
statefullIdentity: true // Maintain state across reconnections
});
```
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:
### Identity Management
```typescript
async function configureCluster(client: CloudlyApiClient, identity) {
// Fetching cluster configuration
const clusterConfig = await client.getClusterConfigFromCloudlyByIdentity(identity);
console.log(`Cluster configuration retrieved:`, clusterConfig);
}
// Get current identity
const currentIdentity = client.identity;
await configureCluster(cloudlyClient, identity);
// Check permissions
if (currentIdentity.permissions.includes('cluster:write')) {
// Perform cluster operations
}
```
The `getClusterConfigFromCloudlyByIdentity` method retrieved the configuration needed to set up and manage your clusters within the multi-cloud environment.
## 📚 Core Operations
### 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:
### 🐳 Image Management
```typescript
async function configureSocketCommunication(client: CloudlyApiClient) {
client.configUpdateSubject.subscribe({
next: (configData) => {
console.log('Received configuration update:', configData);
}
});
// Create an image entry
const image = await client.images.createImage({
name: 'my-app',
description: 'Production application image'
});
client.serverActionSubject.subscribe({
next: (actionRequest) => {
console.log('Server action requested:', actionRequest);
}
});
}
// Push a new version
const imageStream = fs.createReadStream('app.tar');
await image.pushImageVersion('2.0.0', imageStream);
configureSocketCommunication(cloudlyClient);
// List all images
const images = await client.images.listImages();
```
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:
### 🌐 Cluster Operations
```typescript
async function retrieveCertificate(client: CloudlyApiClient, domainName: string, identity) {
const certificate = await client.getCertificateForDomain({
domainName: domainName,
type: 'ssl',
identity: identity
});
// Get cluster configuration
const clusterConfig = await client.getClusterConfigFromCloudlyByIdentity(identity);
console.log('Retrieved SSL Certificate:', certificate);
}
const yourDomain = 'example.com';
await retrieveCertificate(cloudlyClient, yourDomain, identity);
// Deploy to cluster
await client.deployToCluster({
clusterName: 'production',
serviceName: 'api-service',
image: 'my-app:2.0.0',
replicas: 3
});
```
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:
### 🔒 Certificate Management
```typescript
async function cleanup(client: CloudlyApiClient) {
await client.stop();
console.log('Cloudly API client disconnected gracefully.');
}
// Request SSL certificate
const certificate = await client.getCertificateForDomain({
domainName: 'api.example.com',
type: 'ssl',
identity: identity
});
await cleanup(cloudlyClient);
// Use certificate in your application
console.log('Certificate:', certificate.cert);
console.log('Private Key:', certificate.key);
```
By invoking the `stop` method, the API client securely terminates its connection to ensure no resources are left hanging, preventing potential memory leaks.
### 🔐 Secret Management
### Miscellaneous Features
```typescript
// Create secret group
const secretGroup = await client.secrets.createSecretGroup({
name: 'api-secrets',
secrets: [
{ key: 'DATABASE_URL', value: 'postgres://...' },
{ key: 'REDIS_URL', value: 'redis://...' }
]
});
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.
// Retrieve secrets
const secrets = await client.secrets.getSecretGroup('api-secrets');
```
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.
## 🔄 Real-Time Updates
Subscribe to configuration changes and server actions using RxJS:
```typescript
// Listen for configuration updates
client.configUpdateSubject.subscribe({
next: (config) => {
console.log('📡 Configuration updated:', config);
// React to configuration changes
}
});
// Handle server action requests
client.serverActionSubject.subscribe({
next: (action) => {
console.log('⚡ Server action:', action.type);
// Process server-initiated actions
}
});
```
## 🎯 Advanced Usage
### Streaming Operations
```typescript
// Stream logs from a service
const logStream = await client.logs.streamLogs({
service: 'api-service',
follow: true
});
logStream.on('data', (log) => {
console.log(log.message);
});
```
### Batch Operations
```typescript
// Deploy multiple services
const deployments = await Promise.all([
client.deploy({ service: 'frontend', image: 'app:latest' }),
client.deploy({ service: 'backend', image: 'api:latest' }),
client.deploy({ service: 'worker', image: 'worker:latest' })
]);
```
### Error Handling
```typescript
try {
await client.start();
} catch (error) {
if (error.code === 'AUTH_FAILED') {
console.error('Authentication failed:', error.message);
} else if (error.code === 'CONNECTION_LOST') {
console.error('Connection lost, retrying...');
await client.reconnect();
}
}
```
## 🧹 Cleanup
Always gracefully disconnect when done:
```typescript
// Stop the client connection
await client.stop();
console.log('✅ Disconnected cleanly');
```
## 🔌 API Reference
### CloudlyApiClient
Main client class for interacting with Cloudly.
#### Constructor Options
```typescript
interface ICloudlyApiClientOptions {
registerAs: TClientType; // 'api' | 'cli' | 'web'
cloudlyUrl: string; // Full URL including protocol and port
}
```
#### Methods
- `start()` - Initialize connection
- `stop()` - Close connection
- `getIdentityByToken()` - Authenticate with token
- `getClusterConfigFromCloudlyByIdentity()` - Get cluster configuration
- `getCertificateForDomain()` - Request SSL certificate
- `images` - Image management namespace
- `secrets` - Secret management namespace
- `clusters` - Cluster management namespace
## 🎬 Complete Example
```typescript
import { CloudlyApiClient } from '@serve.zone/api';
async function main() {
// Initialize client
const client = new CloudlyApiClient({
registerAs: 'api',
cloudlyUrl: 'https://cloudly.example.com:443'
});
try {
// Connect and authenticate
await client.start();
const identity = await client.getIdentityByToken(process.env.SERVICE_TOKEN, {
tagConnection: true,
statefullIdentity: true
});
// Create and deploy an image
const image = await client.images.createImage({
name: 'my-service',
description: 'Microservice application'
});
// Push image version
const stream = getImageStream(); // Your image stream
await image.pushImageVersion('1.0.0', stream);
// Deploy to cluster
await client.deployToCluster({
clusterName: 'production',
serviceName: 'my-service',
image: 'my-service:1.0.0',
replicas: 3,
environment: {
NODE_ENV: 'production'
}
});
console.log('✅ Deployment successful!');
} catch (error) {
console.error('❌ Error:', error);
} finally {
await client.stop();
}
}
main();
```
## License and Legal Information
@@ -199,4 +303,4 @@ 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.
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.

View 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);
}
}

View File

@@ -1 +1,11 @@
console.log('this is the cli client.');
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
View 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,
}

View File

@@ -1,246 +1,342 @@
# @serve.zone/cli
# @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.
**Command-line interface for Cloudly.** Manage your multi-cloud infrastructure from the terminal with powerful, intuitive commands.
## Install
## 🎯 What is @serve.zone/cli?
To begin using the `@serve.zone/cli` in your projects, install it via npm by running:
The Cloudly CLI brings the full power of the Cloudly platform to your terminal. Whether you're automating deployments, managing secrets, or monitoring services, the CLI provides a streamlined interface for all your cloud operations.
## ✨ Features
- **⚡ Fast & Efficient** - Optimized for speed and minimal resource usage
- **🔐 Secure Authentication** - Token-based authentication with secure storage
- **📝 Intuitive Commands** - Clear, consistent command structure
- **🎨 Formatted Output** - Beautiful, readable output with color coding
- **🔄 Scriptable** - Perfect for CI/CD pipelines and automation
- **📊 Comprehensive** - Access to all Cloudly features from the terminal
## 🚀 Installation
### Global Installation (Recommended)
```bash
npm install @serve.zone/cli --save
pnpm add -g @serve.zone/cli
```
This command will download the package and integrate it into your project's `node_modules` directory, reflecting the dependency in your `package.json`.
### Local Installation
## 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 `Cloudlys` 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 its correct
console.log(`Cloudly is set up at ${cloudlyInstance.config.data.publicUrl}`);
```bash
pnpm add @serve.zone/cli
```
This snippet initializes a Cloudly instance with necessary environment configuration, setting the groundwork for all subsequent CLI operations.
## 🎬 Quick Start
### Core Operations with the CLI
```bash
# Configure your Cloudly instance
servezone config --url https://cloudly.example.com
Here's how you leverage various operational commands within the CLI feature:
# Login with your service token
servezone login --token your-service-token
#### Managing Clusters
# List your clusters
servezone clusters list
To create, list, and delete clusters, youll 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();
# Deploy a service
servezone deploy --cluster production --image myapp:latest
```
By streamlining your service deployments through CLI, you ensure reproducibility and clarity in development operations.
## 🔑 Authentication
#### Managing Certificates
### Initial Setup
Ensuring secure connections by managing SSL certificates is essential. The CLI aids in this through Let's Encrypt integration:
```bash
# Set your Cloudly instance URL
servezone config --url https://cloudly.example.com
```typescript
import { Cloudly } from '@serve.zone/cloudly';
# Authenticate with a service token
servezone login --token YOUR_SERVICE_TOKEN
// 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();
# Or use environment variables
export CLOUDLY_URL=https://cloudly.example.com
export CLOUDLY_TOKEN=YOUR_SERVICE_TOKEN
```
This process facilitates the automation of SSL certificates provisioning, ensuring high security in your apps.
### Managing Profiles
### Automating Tasks with the CLI
```bash
# Create a profile for different environments
servezone profile create production --url https://prod.cloudly.com
servezone profile create staging --url https://stage.cloudly.com
Task scheduling is a feature you can utilize to automate recurring processes. Heres an example of how `@serve.zone/cli` accomplishes task scheduling:
# Switch between profiles
servezone profile use production
```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();
# List all profiles
servezone profile list
```
Scheduled tasks like periodic maintenance, data synchronization, or backups ensure you keep your cloud environment robust and reliable.
## 📚 Core Commands
### Integrating Third-Party APIs
### 🌐 Cluster Management
Expand the scope of your applications with API integrations offered via `@serve.zone/cli`:
```bash
# List all clusters
servezone clusters list
```typescript
import { Cloudly } from '@serve.zone/cloudly';
# Get cluster details
servezone clusters info production-cluster
// Function to send notifications
async function sendNotification() {
const cloudlyConfig = {
cfToken: 'your-cloudflare-token',
hetznerToken: 'your-hetzner-token',
};
# Create a new cluster
servezone clusters create \
--name production-cluster \
--region eu-central \
--nodes 3
const cloudly = new Cloudly(cloudlyConfig);
await cloudly.start();
# Scale a cluster
servezone clusters scale production-cluster --nodes 5
// Configure and send push notification
await cloudly.externalApiManager.sendPushMessage({
deviceToken: 'some_device_token',
message: 'Hello from Cloudly!',
});
}
sendNotification();
# Delete a cluster
servezone clusters delete staging-cluster
```
API integrations via the CLI extend Cloudlys reach, enabling comprehensive service interconnections.
### 🐳 Service Deployment
### Security and Access Management
```bash
# Deploy a service
servezone deploy \
--cluster production \
--name api-service \
--image myapp:2.0.0 \
--replicas 3 \
--port 80:3000
Effective identity management is possible through `@serve.zone/cli`. Manage user roles, token validations, and more:
# Update a service
servezone service update api-service \
--image myapp:2.1.0 \
--replicas 5
```typescript
import { Cloudly } from '@serve.zone/cloudly';
# Scale a service
servezone service scale api-service --replicas 10
// 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();
# Remove a service
servezone service remove api-service
```
The applications of identity validation streamline operational security and enforce access controls across your systems.
### 🔐 Secret Management
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.
```bash
# Create a secret
servezone secrets create \
--name database-url \
--value "postgres://user:pass@host/db"
# Create a secret group
servezone secrets create-group \
--name api-secrets \
--secret DATABASE_URL=postgres://... \
--secret REDIS_URL=redis://...
# List secrets
servezone secrets list
# Get secret value
servezone secrets get database-url
# Delete a secret
servezone secrets delete old-secret
```
### 📦 Image Management
```bash
# List images
servezone images list
# Push a new image
servezone images push \
--name myapp \
--version 2.0.0 \
--file ./myapp.tar
# Tag an image
servezone images tag myapp:2.0.0 myapp:latest
# Delete an image
servezone images delete myapp:1.0.0
```
### 📊 Monitoring & Logs
```bash
# View service logs
servezone logs api-service
# Follow logs in real-time
servezone logs api-service --follow
# Filter logs
servezone logs api-service --since 1h --grep ERROR
# Get service status
servezone service status api-service
# Monitor cluster health
servezone clusters health production-cluster
```
### 🔧 DNS Management
```bash
# List DNS records
servezone dns list --domain example.com
# Create a DNS record
servezone dns create \
--domain example.com \
--name api \
--type A \
--value 192.168.1.1
# Update a DNS record
servezone dns update api.example.com --value 192.168.1.2
# Delete a DNS record
servezone dns delete old.example.com
```
## 🎯 Advanced Usage
### Environment Variables
```bash
# Set environment variables for a service
servezone deploy \
--name api-service \
--env NODE_ENV=production \
--env PORT=3000 \
--env DATABASE_URL=@secret:database-url
```
### Configuration Files
Create a `cloudly.yaml` file:
```yaml
cluster: production
service:
name: api-service
image: myapp:latest
replicas: 3
ports:
- 80:3000
environment:
NODE_ENV: production
DATABASE_URL: "@secret:database-url"
```
Deploy using the config file:
```bash
servezone deploy --config cloudly.yaml
```
### Batch Operations
```bash
# Deploy multiple services
servezone deploy --config services/*.yaml
# Update all services in a namespace
servezone service update --namespace api --image-tag v2.0.0
# Delete all staging resources
servezone cleanup --environment staging
```
## 🔄 CI/CD Integration
### GitHub Actions
```yaml
- name: Deploy to Cloudly
run: |
servezone config --url ${{ secrets.CLOUDLY_URL }}
servezone login --token ${{ secrets.CLOUDLY_TOKEN }}
servezone deploy \
--cluster production \
--name api-service \
--image myapp:${{ github.sha }}
```
### GitLab CI
```yaml
deploy:
script:
- servezone config --url $CLOUDLY_URL
- servezone login --token $CLOUDLY_TOKEN
- servezone deploy --config cloudly.yaml
```
## 🎨 Output Formats
```bash
# JSON output for scripting
servezone clusters list --output json
# YAML output
servezone service info api-service --output yaml
# Table output (default)
servezone images list --output table
# Quiet mode (IDs only)
servezone clusters list --quiet
```
## 🛠️ Troubleshooting
```bash
# Enable debug output
servezone --debug clusters list
# Check CLI version
servezone version
# Test connection
servezone ping
# View configuration
servezone config show
# Clear cache and credentials
servezone logout --clear-cache
```
## 📝 Command Reference
```bash
servezone --help # Show all commands
servezone <command> --help # Show command-specific help
servezone clusters --help # Show cluster commands
servezone service --help # Show service commands
servezone secrets --help # Show secret commands
```
## 🔌 Shell Completion
Enable tab completion for your shell:
```bash
# Bash
servezone completion bash > /etc/bash_completion.d/servezone
# Zsh
servezone completion zsh > ~/.zsh/completions/_servezone
# Fish
servezone completion fish > ~/.config/fish/completions/servezone.fish
```
## License and Legal Information
@@ -259,4 +355,4 @@ 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.
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.

View File

@@ -1,8 +1,15 @@
{
"name": "@serve.zone/cli",
"dependencies": [],
"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"]
}

View File

@@ -1,12 +1,11 @@
import * as plugins from '../plugins.js';
/**
* results from a DeploymentDirective
* a deployment happens when a service is deployed
* tracks the status of a deployment
*/
export interface IDeployment {
id: string;
deploymentDirectiveId: string;
affectedServiceIds: string[];
usedImageId: string;
deploymentLog: string[];

View File

@@ -1,14 +0,0 @@
import type { IServiceRessources } from "./docker.js";
/**
* used for tellilng a cluster about a disired deployment
* and specifies its configuration
*/
export interface IDeploymentDirective {
id: string;
name: string;
imageClaim: string;
ports: { hostPort: number; containerPort: number }[];
environment: { [key: string]: string };
resources?: IServiceRessources;
}

View File

@@ -1,6 +0,0 @@
export interface IEnvBundle {
environment: string;
timeSensitive: boolean;
configKeyValueObject: {[key: string]: string};
}

View 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;
};
}

View File

@@ -4,6 +4,11 @@ export interface IImage {
id: string;
data: {
name: string;
location: {
internal: boolean;
externalRegistryId: string;
externalImageTag: string;
}
description: string;
versions: Array<{
versionString: string;

View File

@@ -2,10 +2,9 @@ export * from './cloudlyconfig.js';
export * from './cluster.js';
export * from './config.js';
export * from './deployment.js';
export * from './deploymentdirective.js';
export * from './docker.js';
export * from './env.js';
export * from './event.js';
export * from './externalregistry.js';
export * from './image.js';
export * from './secretbundle.js';
export * from './secretgroup.js'

View File

@@ -7,16 +7,33 @@ export interface ISecretBundle {
/**
* 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[];
/**
* You can add specific tags using this
* access to this secretBundle also grants access to resources with matching tags
*/
includedTags: {
key: string;
@@ -24,19 +41,21 @@ export interface ISecretBundle {
}[];
/**
* add images
* access to this secretBundle also grants access to the images
*/
includedImages: {
imageClaims: {
imageId: string;
permissions: ('read' | 'write')[];
}[];
}[];
/**
* authrozations select a specific environment of a config bundle
*/
authorizations: Array<{
secretAccessKey: string;
environment: string;
}>;
authorizations: Array<ISecretBundleAuthorization>;
};
}
export interface ISecretBundleAuthorization {
secretAccessKey: string;
environment: string;
}

View File

@@ -4,10 +4,19 @@ 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: {
@@ -21,6 +30,5 @@ export interface IService {
protocol?: 'http' | 'https' | 'ssh';
}[];
deploymentIds: string[];
deploymentDirectiveIds: string[];
};
}

View File

@@ -2,9 +2,9 @@ import * as plugins from '../plugins.js';
export type TTemplates = 'default' | 'linkaction' | 'notification';
export interface IRequest_SendEmail extends plugins.typedrequestInterfaces.implementsTR<
export interface IReq_SendEmail extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IRequest_SendEmail
IReq_SendEmail
> {
method: 'sendEmail';
request: {
@@ -25,9 +25,9 @@ export interface IRequest_SendEmail extends plugins.typedrequestInterfaces.imple
};
}
export interface IRequestRegisterRecipient extends plugins.typedrequestInterfaces.implementsTR<
export interface IReq_RegisterRecipient extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IRequestRegisterRecipient
IReq_RegisterRecipient
> {
method: 'registerRecepient';
request: {
@@ -37,3 +37,34 @@ export interface IRequestRegisterRecipient extends plugins.typedrequestInterface
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;
};
}

View File

@@ -1,207 +1,375 @@
# @serve.zone/interfaces
# @serve.zone/interfaces 📋
interfaces for working with containers
**TypeScript interfaces for the Cloudly ecosystem.** Type-safe contracts for multi-cloud infrastructure management.
## Install
## 🎯 What is @serve.zone/interfaces?
To install `@serve.zone/interfaces`, run the following command in your terminal:
This package provides the complete set of TypeScript interfaces that power the Cloudly platform. It ensures type safety and consistency across all components - from API requests to data models, from service definitions to infrastructure configurations.
## ✨ Features
- **🔒 Type Safety** - Comprehensive TypeScript interfaces for all Cloudly operations
- **📦 Modular Structure** - Organized by domain for easy navigation
- **🔄 Version Compatibility** - Interfaces versioned with the platform
- **📚 Well Documented** - Each interface includes JSDoc comments
- **🎭 Multi-Purpose** - Used by API clients, CLI tools, and web interfaces
- **✅ Validation Ready** - Compatible with runtime type checking libraries
## 🚀 Installation
```bash
npm install @serve.zone/interfaces --save
pnpm add @serve.zone/interfaces
```
This will add `@serve.zone/interfaces` to your project's dependencies, allowing you to import and use various predefined interfaces that facilitate container operations and interactions within the ServeZone ecosystem.
## 🏗️ Interface Categories
## Usage
### 📡 Request/Response Interfaces
The `@serve.zone/interfaces` module provides a robust set of TypeScript interfaces designed to standardize interaction with various services and components in a cloud-native environment. The interfaces are targeted at simplifying the integration process with container orchestration, network configurations, logging, and service definitions. The module is particularly useful if you're working on infrastructure or service orchestration solutions using Node.js and TypeScript.
This document guides you through a comprehensive use case scenario of `@serve.zone/interfaces`. We will cover how to effectively utilize these interfaces to set up cloud services, manage application configurations, and handle system-related communications. This tutorial will explore various feature sets within the module, focusing on real-world implementations and practical coding strategies.
### Prerequisites
Before diving in, make sure to satisfy the following prerequisites:
- **Node.js**: Ensure you have Node.js installed (preferably the latest LTS version).
- **TypeScript**: Your environment should support TypeScript, as this module leverages strong typing offered by TypeScript.
- **Cloud Account Access**: Some of the interfaces interact with live cloud services; thus, ensure you have necessary credentials (like API tokens) available for testing or integration.
### Core Interfaces and Scenarios
#### 1. Handling Typed Requests
One fundamental aspect is defining typed requests, which standardizes API call definitions across different microservices or components. The module offers interfaces such as `IRequest_GetAllImages`, `IRequest_CreateCluster`, that you can extend or implement within your service logic to ensure strong typing and consistency.
Typed contracts for API communication:
```typescript
import { IRequest_GetAllImages } from '@serve.zone/interfaces/requests/image';
import {
IRequest_GetAllImages,
IRequest_CreateCluster,
IRequest_DeployService
} from '@serve.zone/interfaces';
class ImageService {
private cloudlyClient;
constructor(cloudlyClient: CloudlyApiClient) {
this.cloudlyClient = cloudlyClient;
// Type-safe request
const request: IRequest_GetAllImages['request'] = {
identity: userIdentity,
filters: {
tag: 'production'
}
};
public async fetchAllImages() {
const request: IRequest_GetAllImages['request'] = {
identity: this.cloudlyClient.identity,
};
const response = await this.cloudlyClient.typedsocketClient.fireTypedRequest<IRequest_GetAllImages>(request);
return response.images;
}
}
// Type-safe response
const response: IRequest_GetAllImages['response'] = {
images: [...]
};
```
In the above code, we structured a simple function to retrieve all images from a service, assuming the `cloudlyClient` is your authenticated API client. The typed request interface ensures that both the request and response align with the expected types.
### 📦 Data Models
#### 2. Logging and Smart Logging Interfaces
Logging is a crucial aspect of cloud applications. The module provides interfaces to assist in integrating logging systems like `@push.rocks/smartlog-interfaces`.
Core data structures for Cloudly entities:
```typescript
import { ILogger, ILogConfig } from '@push.rocks/smartlog-interfaces';
import {
ICluster,
IService,
IImage,
ISecret,
IServer
} from '@serve.zone/interfaces';
class LoggerService {
private logger: ILogger;
constructor(logConfig: ILogConfig) {
this.logger = new SmartLogger(logConfig);
// Define a service
const service: IService = {
id: 'service-123',
data: {
name: 'api-service',
imageId: 'image-456',
imageVersion: '2.0.0',
environment: {
NODE_ENV: 'production'
},
scaleFactor: 3,
balancingStrategy: 'round-robin',
ports: {
web: 80,
metrics: 9090
},
domains: [
{ name: 'api.example.com' }
],
deploymentIds: [],
deploymentDirectiveIds: []
}
public logMessage(logPackage: ILogPackage) {
this.logger.log(logPackage);
}
}
};
```
This illustrates a logger service utilizing `ILogConfig` to configure and initiate a logging mechanism. You can log structured data using `logPackage`, thus enhancing traceability and debugging efficiency.
### 🔐 Authentication & Identity
#### 3. Container Service Management
Managing containers, particularly when dealing with microservices, can be complex, but interfaces like `IService`, `ICluster`, and `IServer` aid in structuring container service management.
Identity management interfaces:
```typescript
import { IService } from '@serve.zone/interfaces/data/service';
import {
IIdentity,
IServiceToken,
IPermission
} from '@serve.zone/interfaces';
function defineService(): IService {
return {
id: 'unique-service-id',
data: {
name: 'my-container-service',
imageId: 'unique-image-id',
imageVersion: '1.0.0',
environment: { KEY: 'VALUE' },
secretBundleId: 'bundle-id',
scaleFactor: 2,
balancingStrategy: 'round-robin',
ports: { web: 80 },
domains: [{ name: 'example.com' }],
deploymentIds: [],
deploymentDirectiveIds: [],
}
};
}
const identity: IIdentity = {
id: 'user-789',
name: 'service-account',
type: 'service',
permissions: ['cluster:read', 'service:write'],
tokenHash: 'hashed-token',
metadata: {
createdAt: new Date(),
lastAccess: new Date()
}
};
```
In the example, a service definition is drafted, encapsulating critical service metadata, including its environment variables, domain configuration, and load balancing strategy. Adhering to `IService` ensures that all necessary service data is encapsulated correctly.
### 🌐 Network Configuration
#### 4. Network Configuration and Routing
Networking is integral to cloud-native applications. Interfaces in `@serve.zone/interfaces` help shape network interaction patterns.
Networking and routing interfaces:
```typescript
import { IReverseProxyConfig } from '@serve.zone/interfaces/data/traffic';
import {
IReverseProxyConfig,
IDomainConfig,
ILoadBalancerConfig
} from '@serve.zone/interfaces';
const proxyConfig: IReverseProxyConfig = {
domain: 'example.com',
path: '/',
serviceAddress: 'http://service:8080',
domain: 'app.example.com',
path: '/api',
serviceAddress: 'http://api-service:3000',
ssl: true,
headers: {
'X-Real-IP': '$remote_addr'
}
};
function configureProxy() {
// Logic to apply the proxyConfig, potentially using Typedi, Smartclient, or similar libraries.
}
```
Here, `IReverseProxyConfig` is used to define a reverse proxy for a service. Such configurations are necessary for routing external requests into internal services securely.
### 📊 Monitoring & Metrics
### Advanced Interface Utilization
#### Monitoring and Metrics Collection
For observability, you can track system metrics using `IServerMetrics` or cluster status interfaces.
Observability interfaces:
```typescript
import { IServerMetrics } from '@serve.zone/interfaces/data/server';
import {
IServerMetrics,
IServiceMetrics,
IClusterHealth
} from '@serve.zone/interfaces';
function reportMetrics(metrics: IServerMetrics) {
console.log(`CPU Usage: ${metrics.cpuUsageInPercent}%`);
console.log(`Memory Usage: ${metrics.memoryUsageinMB}MB`);
}
const sampleMetrics: IServerMetrics = {
serverId: 'server-123',
cpuUsageInPercent: 45,
memoryUsageinMB: 2048,
memoryAvailableInMB: 4096,
containerCount: 10,
containerMetrics: [],
const metrics: IServerMetrics = {
serverId: 'server-001',
cpuUsageInPercent: 65,
memoryUsageinMB: 3072,
memoryAvailableInMB: 8192,
diskUsageInPercent: 40,
networkInMbps: 100,
networkOutMbps: 150,
containerCount: 12,
containerMetrics: [...]
};
reportMetrics(sampleMetrics);
```
Implementing such metrics tracking provides insight into performance bottlenecks and helps strategize scaling decisions.
### 🔒 Secret Management
#### Certificate Management
To handle SSL certificates programmatically, utilize interfaces such as `IRequest_Any_Cloudly_GetCertificateForDomain`.
Security and credential interfaces:
```typescript
import { IRequest_Any_Cloudly_GetCertificateForDomain } from '@serve.zone/interfaces/requests/certificate';
import {
ISecretGroup,
ISecretBundle,
IEncryptedData
} from '@serve.zone/interfaces';
async function fetchCertificate(cloudlyClient: CloudlyApiClient, domainName: string) {
const request: IRequest_Any_Cloudly_GetCertificateForDomain['request'] = {
identity: cloudlyClient.identity,
domainName: domainName,
type: 'ssl'
};
const secretGroup: ISecretGroup = {
id: 'secrets-123',
name: 'database-credentials',
secrets: [
{
key: 'DB_HOST',
value: 'encrypted-value',
encrypted: true
},
{
key: 'DB_PASSWORD',
value: 'encrypted-value',
encrypted: true
}
],
metadata: {
environment: 'production',
service: 'api'
}
};
```
return await cloudlyClient.typedsocketClient.fireTypedRequest<IRequest_Any_Cloudly_GetCertificateForDomain>(request);
## 📚 Common Usage Patterns
### Creating Type-Safe API Clients
```typescript
import {
IRequest_CreateService,
IService
} from '@serve.zone/interfaces';
class ServiceClient {
async createService(
serviceData: IService['data']
): Promise<IService> {
const request: IRequest_CreateService['request'] = {
identity: this.identity,
serviceData
};
const response = await this.client.send<IRequest_CreateService>(
'createService',
request
);
return response.service;
}
}
```
Managing certificates dynamically via typed requests simplifies deployment and automates the security dimensions of your applications.
#### Integrating with External Messaging Services
Use `IRequest_SendEmail` to integrate platform services for sending emails:
### Validating Incoming Data
```typescript
import { IRequest_SendEmail } from '@serve.zone/interfaces/platformservice/mta';
import { ICluster } from '@serve.zone/interfaces';
import { validateType } from 'your-validation-library';
async function sendNotification(emailClient: any) {
const emailRequest: IRequest_SendEmail['request'] = {
title: 'Welcome to ServeZone!',
from: 'service@company.com',
to: 'user@example.com',
body: '<h1>Congratulations</h1><p>Your account has been created successfully.</p>',
};
await emailClient.sendEmail(emailRequest);
function validateClusterData(data: unknown): ICluster {
if (!validateType<ICluster>(data)) {
throw new Error('Invalid cluster data');
}
return data;
}
```
This approach demonstrates abstracting the email sending functionality using typed interfaces, contributing to code consistency and robustness.
### Building Configuration Objects
### Conclusion
```typescript
import {
ICloudlyConfig,
IMongoDescriptor
} from '@serve.zone/interfaces';
The `@serve.zone/interfaces` module equips developers with a set of interfaces tailored for managing containers, orchestrating cloud services, and handling system interactions seamlessly. By applying these interfaces, projects can achieve coherence, reduce coupling, and simplify the integration process across various service domains.
const config: ICloudlyConfig = {
cfToken: process.env.CF_TOKEN!,
hetznerToken: process.env.HETZNER_TOKEN!,
environment: 'production',
letsEncryptEmail: 'certs@example.com',
publicUrl: 'cloudly.example.com',
publicPort: 443,
mongoDescriptor: {
mongoDbUrl: process.env.MONGO_URL!,
mongoDbName: 'cloudly',
mongoDbUser: process.env.MONGO_USER!,
mongoDbPass: process.env.MONGO_PASS!
}
};
```
Focusing on practical applications, try extending these interfaces to suit additional requirements in your projects. Engage actively with the module community, or contribute new ideas to enhance the breadth and depth of this interface library. Explore the integration patterns showcased here and contribute toward a sophisticated cloud-native development framework.
## 🎯 Advanced Features
### Generic Request Handler
```typescript
import { ITypedRequest } from '@serve.zone/interfaces';
class RequestHandler {
async handle<T extends ITypedRequest>(
request: T['request']
): Promise<T['response']> {
// Type-safe request handling
return this.processRequest(request);
}
}
```
### Discriminated Unions
```typescript
import { IDeploymentStatus } from '@serve.zone/interfaces';
function handleStatus(status: IDeploymentStatus) {
switch (status.type) {
case 'pending':
console.log('Deployment pending...');
break;
case 'running':
console.log(`Running on ${status.serverId}`);
break;
case 'failed':
console.log(`Failed: ${status.error}`);
break;
}
}
```
### Extending Interfaces
```typescript
import { IService } from '@serve.zone/interfaces';
interface IExtendedService extends IService {
customMetadata: {
team: string;
costCenter: string;
sla: 'standard' | 'premium';
};
}
```
## 🔄 Version Compatibility
| @serve.zone/interfaces | @serve.zone/cloudly | @serve.zone/api | @serve.zone/cli |
|------------------------|---------------------|-----------------|-----------------|
| 5.x | 5.x | 5.x | 5.x |
| 4.x | 4.x | 4.x | 4.x |
| 3.x | 3.x | 3.x | 3.x |
## 📖 Interface Documentation
All interfaces include comprehensive JSDoc comments:
```typescript
/**
* Represents a cluster configuration
* @interface ICluster
*/
export interface ICluster {
/**
* Unique identifier for the cluster
* @type {string}
*/
id: string;
/**
* Cluster configuration data
* @type {IClusterData}
*/
data: IClusterData;
}
```
## 🛠️ Development Tips
### Import Organization
```typescript
// Group imports by category
import {
// Data models
ICluster,
IService,
IImage,
// Requests
IRequest_CreateCluster,
IRequest_DeployService,
// Configuration
ICloudlyConfig,
IReverseProxyConfig
} from '@serve.zone/interfaces';
```
### Type Guards
```typescript
import { IService, ICluster } from '@serve.zone/interfaces';
function isService(entity: IService | ICluster): entity is IService {
return 'imageId' in entity.data;
}
```
## License and Legal Information
@@ -220,4 +388,4 @@ 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.
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.

View File

@@ -0,0 +1,16 @@
import * as plugins from '../plugins.js';
import * as userInterfaces from '../data/user.js';
export interface IReq_Admin_LoginWithUsernameAndPassword extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_Admin_LoginWithUsernameAndPassword
> {
method: 'adminLoginWithUsernameAndPassword';
request: {
username: string;
password: string;
};
response: {
identity: userInterfaces.IIdentity;
}
}

View File

@@ -5,11 +5,11 @@ import * as plugins from '../plugins.js';
/**
* get all clusters
*/
export interface IRequest_GetAllClusters extends plugins.typedrequestInterfaces.implementsTR<
export interface IReq_Any_Cloudly_GetClusters extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IRequest_GetAllClusters
IReq_Any_Cloudly_GetClusters
> {
method: 'getAllClusters';
method: 'getClusters';
request: {
identity: userInterfaces.IIdentity;
};
@@ -18,6 +18,21 @@ export interface IRequest_GetAllClusters extends plugins.typedrequestInterfaces.
};
}
export interface IReq_Any_Cloudly_GetClusterById
extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_Any_Cloudly_GetClusterById
> {
method: 'getClusterById';
request: {
identity: userInterfaces.IIdentity;
clusterId: string;
};
response: {
cluster: clusterInterfaces.ICluster;
};
}
export interface IRequest_CreateCluster extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IRequest_CreateCluster
@@ -28,40 +43,40 @@ export interface IRequest_CreateCluster extends plugins.typedrequestInterfaces.i
clusterName: string;
};
response: {
clusterConfig: clusterInterfaces.ICluster;
cluster: clusterInterfaces.ICluster;
};
}
/**
* updates a cluster
*/
export interface IRequest_UpdateCluster extends plugins.typedrequestInterfaces.implementsTR<
export interface IReq_Any_Cloudly_UpdateCluster extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IRequest_UpdateCluster
IReq_Any_Cloudly_UpdateCluster
> {
method: 'updateCluster';
request: {
identity: userInterfaces.IIdentity;
clusterConfig: clusterInterfaces.ICluster;
clusterData: clusterInterfaces.ICluster['data'];
};
response: {
clusterConfig: clusterInterfaces.ICluster;
resultCluster: clusterInterfaces.ICluster;
};
}
/**
* deletes a cluster
*/
export interface IRequest_DeleteCluster extends plugins.typedrequestInterfaces.implementsTR<
export interface IReq_Any_Cloudly_DeleteClusterById extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IRequest_DeleteCluster
IReq_Any_Cloudly_DeleteClusterById
> {
method: 'deleteCluster';
method: 'deleteClusterById';
request: {
identity: userInterfaces.IIdentity;
clusterId: string;
};
response: {
success: boolean;
ok: boolean;
};
}

View File

@@ -3,7 +3,6 @@ import * as clusterInterfaces from '../data/cluster.js';
import * as serverInterfaces from '../data/server.js';
import * as userInterfaces from '../data/user.js';
import type { IService } from '../data/service.js';
import type { IDeploymentDirective } from '../data/deploymentdirective.js';
export interface IRequest_Any_Cloudly_GetServerConfig
extends plugins.typedrequestInterfaces.implementsTR<
@@ -31,7 +30,7 @@ extends plugins.typedrequestInterfaces.implementsTR<
};
response: {
configData: clusterInterfaces.ICluster;
deploymentDirectives: IDeploymentDirective[];
services: IService[];
};
}
@@ -43,7 +42,7 @@ extends plugins.typedrequestInterfaces.implementsTR<
method: 'pushClusterConfig';
request: {
configData: clusterInterfaces.ICluster;
deploymentDirectives: IDeploymentDirective[];
services: IService[];
};
response: {};
}

View File

@@ -0,0 +1,72 @@
import * as plugins from '../plugins.js';
import * as data from '../data/index.js';
import * as userInterfaces from '../data/user.js';
export interface IReq_GetRegistryById extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetRegistryById
> {
method: 'getExternalRegistryById';
request: {
identity: userInterfaces.IIdentity;
id: string;
};
response: {
registry: data.IExternalRegistry;
};
}
export interface IReq_GetRegistries extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetRegistries
> {
method: 'getExternalRegistries';
request: {
identity: userInterfaces.IIdentity;
};
response: {
registries: data.IExternalRegistry[];
};
}
export interface IReq_CreateRegistry extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_CreateRegistry
> {
method: 'createExternalRegistry';
request: {
identity: userInterfaces.IIdentity;
registryData: data.IExternalRegistry['data'];
};
response: {
registry: data.IExternalRegistry;
};
}
export interface IReq_UpdateRegistry extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_UpdateRegistry
> {
method: 'updateExternalRegistry';
request: {
identity: userInterfaces.IIdentity;
registryData: data.IExternalRegistry['data'];
};
response: {
resultRegistry: data.IExternalRegistry;
};
}
export interface IReq_DeleteRegistryById extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_DeleteRegistryById
> {
method: 'deleteExternalRegistryById';
request: {
identity: userInterfaces.IIdentity;
registryId: string;
};
response: {
ok: boolean;
};
}

View File

@@ -18,6 +18,7 @@ export interface IRequest_GetAllImages extends plugins.typedrequestInterfaces.im
/**
* gets a single image
* authentication can happen via imageClaim or identity
*/
export interface IRequest_GetImage extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,

View File

@@ -1,31 +1,39 @@
import * as plugins from '../plugins.js';
import * as adminRequests from './admin.js';
import * as certificateRequests from './certificate.js';
import * as clusterRequests from './cluster.js';
import * as configRequests from './config.js';
import * as externalRegistryRequests from './externalregistry.js';
import * as identityRequests from './identity.js';
import * as imageRequests from './image.js';
import * as informRequests from './inform.js';
import * as logRequests from './log.js';
import * as networkRequests from './network.js';
import * as routingRequests from './routing.js';
import * as secretRequests from './secret.js';
import * as secretBundleRequests from './secretbundle.js';
import * as secretGroupRequests from './secretgroup.js';
import * as serverRequests from './server.js';
import * as serviceRequests from './service.js';
import * as statusRequests from './status.js';
import * as versionRequests from './version.js';
export {
adminRequests as admin,
certificateRequests as certificate,
clusterRequests as cluster,
configRequests as config,
externalRegistryRequests as externalRegistry,
identityRequests as identity,
imageRequests as image,
informRequests as inform,
logRequests as log,
networkRequests as network,
routingRequests as routing,
secretRequests as secret,
secretBundleRequests as secretbundle,
secretGroupRequests as secretgroup,
serverRequests as server,
serviceRequests as service,
statusRequests as status,
versionRequests as version,
};

View File

@@ -1,94 +0,0 @@
import * as plugins from '../plugins.js';
import * as data from '../data/index.js';
import * as userInterfaces from '../data/user.js';
export interface IReq_GetEnvBundle extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetEnvBundle
> {
method: 'getEnvBundle';
request: {
authorization: string;
/**
* specify this if you want to get a warning, if the envBundle is for an unexpected environment
*/
environment?: string;
};
response: {
envBundle: data.IEnvBundle;
};
}
export interface IReq_Admin_LoginWithUsernameAndPassword extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_Admin_LoginWithUsernameAndPassword
> {
method: 'adminLoginWithUsernameAndPassword';
request: {
username: string;
password: string;
};
response: {
identity: userInterfaces.IIdentity;
}
}
export interface IReq_Admin_GetConfigBundlesAndSecretGroups extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_Admin_GetConfigBundlesAndSecretGroups
> {
method: 'adminGetConfigBundlesAndSecretGroups';
request: {
identity: userInterfaces.IIdentity;
};
response: {
secretBundles: data.ISecretBundle[];
secretGroups: data.ISecretGroup[];
};
}
export interface IReq_Admin_CreateConfigBundlesAndSecretGroups extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_Admin_CreateConfigBundlesAndSecretGroups
> {
method: 'adminCreateConfigBundlesAndSecretGroups';
request: {
identity: userInterfaces.IIdentity;
secretBundles: data.ISecretBundle[];
secretGroups: data.ISecretGroup[];
};
response: {
ok: boolean;
};
}
export interface IReq_Admin_UpdateConfigBundlesAndSecretGroups extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_Admin_UpdateConfigBundlesAndSecretGroups
> {
method: 'adminUpdateConfigBundlesAndSecretGroups';
request: {
identity: userInterfaces.IIdentity;
configBundles: data.ISecretBundle[];
secretGroups: data.ISecretGroup[];
};
response: {
ok: boolean;
};
}
export interface IReq_Admin_DeleteConfigBundlesAndSecretGroups extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_Admin_DeleteConfigBundlesAndSecretGroups
> {
method: 'adminDeleteConfigBundlesAndSecretGroups';
request: {
identity: userInterfaces.IIdentity;
secretBundleIds: string[];
secretGroupIds: string[];
};
response: {
ok: boolean;
};
}

View File

@@ -0,0 +1,103 @@
import * as plugins from '../plugins.js';
import * as data from '../data/index.js';
import * as userInterfaces from '../data/user.js';
export interface IReq_GetSecretBundles extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetSecretBundles
> {
method: 'getSecretBundles';
request: {
identity: userInterfaces.IIdentity;
};
response: {
secretBundles: data.ISecretBundle[];
};
}
export interface IReq_GetSecretBundleById extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetSecretBundleById
> {
method: 'getSecretBundleById';
request: {
identity: userInterfaces.IIdentity;
secretBundleId: string;
};
response: {
secretBundle: data.ISecretBundle;
};
}
export interface IReq_CreateSecretBundle extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_CreateSecretBundle
> {
method: 'createSecretBundle';
request: {
identity: userInterfaces.IIdentity;
secretBundle: data.ISecretBundle;
};
response: {
resultSecretBundle: data.ISecretBundle;
};
}
export interface IReq_UpdateSecretBundle extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_UpdateSecretBundle
> {
method: 'updateSecretBundle';
request: {
identity: userInterfaces.IIdentity;
secretBundle: data.ISecretBundle;
};
response: {
resultSecretBundle: data.ISecretBundle;
};
}
export interface IReq_DeleteSecretBundleById extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_DeleteSecretBundleById
> {
method: 'deleteSecretBundleById';
request: {
identity: userInterfaces.IIdentity;
secretBundleId: string;
};
response: {
ok: boolean;
};
}
export interface IReq_GetSecretBundleByAuthorization extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetSecretBundleByAuthorization
> {
method: 'getSecretBundleByAuthorization';
request: {
identity: userInterfaces.IIdentity;
secretBundleAuthorization: data.ISecretBundleAuthorization;
};
response: {
secretBundle: data.ISecretBundle;
};
}
export interface IReq_GetFlatKeyValueObject extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetFlatKeyValueObject
> {
method: 'getFlatKeyValueObject';
request: {
identity: userInterfaces.IIdentity;
seccretBundleId: string;
secretBundleAuthorization: data.ISecretBundleAuthorization;
};
response: {
flatKeyValueObject: {[key: string]: string};
};
}

View File

@@ -0,0 +1,74 @@
import * as plugins from '../plugins.js';
import * as data from '../data/index.js';
import * as userInterfaces from '../data/user.js';
export interface IReq_GetSecretGroups extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetSecretGroups
> {
method: 'getSecretGroups';
request: {
identity: userInterfaces.IIdentity;
};
response: {
secretGroups: data.ISecretGroup[];
};
}
export interface IReq_GetSecretGroupById extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetSecretGroupById
> {
method: 'getSecretGroupById';
request: {
identity: userInterfaces.IIdentity;
secretGroupId: string;
};
response: {
secretGroup: data.ISecretGroup;
};
}
export interface IReq_CreateSecretGroup extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_CreateSecretGroup
> {
method: 'createSecretGroup';
request: {
identity: userInterfaces.IIdentity;
secretGroup: data.ISecretGroup;
};
response: {
resultSecretGroup: data.ISecretGroup;
};
}
export interface IReq_UpdateSecretGroup extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_UpdateSecretGroup
> {
method: 'updateSecretGroup';
request: {
identity: userInterfaces.IIdentity;
secretGroup: data.ISecretGroup;
};
response: {
resultSecretGroup: data.ISecretGroup;
};
}
export interface IReq_DeleteSecretGroupById extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_DeleteSecretGroupById
> {
method: 'deleteSecretGroupById';
request: {
identity: userInterfaces.IIdentity;
secretGroupId: string;
};
response: {
ok: boolean;
};
}

View File

@@ -0,0 +1,95 @@
import * as plugins from '../plugins.js';
import type { IService } from '../data/service.js';
import type { IIdentity } from '../data/user.js';
import type { IServiceRessources } from '../data/docker.js';
export interface IRequest_Any_Cloudly_GetServiceById
extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IRequest_Any_Cloudly_GetServiceById
> {
method: 'getServiceById';
request: {
identity: IIdentity;
serviceId: string;
};
response: {
service: IService;
};
}
export interface IRequest_Any_Cloudly_GetServices
extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IRequest_Any_Cloudly_GetServices
> {
method: 'getServices';
request: {
identity: IIdentity;
};
response: {
services: IService[];
};
}
export interface IRequest_Any_Cloudly_CreateService
extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IRequest_Any_Cloudly_CreateService
> {
method: 'createService';
request: {
identity: IIdentity;
serviceData: IService['data'];
};
response: {
service: IService;
};
}
export interface IRequest_Any_Cloudly_UpdateService
extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IRequest_Any_Cloudly_UpdateService
> {
method: 'updateService';
request: {
identity: IIdentity;
serviceId: string;
serviceData: IService['data'];
};
response: {
service: IService;
};
}
export interface IRequest_Any_Cloudly_DeleteServiceById
extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IRequest_Any_Cloudly_DeleteServiceById
> {
method: 'deleteServiceById';
request: {
identity: IIdentity;
serviceId: string;
};
response: {
success: boolean;
};
}
export interface IRequest_Any_Cloudly_GetServiceSecretBundlesAsFlatObject
extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IRequest_Any_Cloudly_GetServiceSecretBundlesAsFlatObject
> {
method: 'getServiceSecretBundlesAsFlatObject';
request: {
identity: IIdentity;
serviceId: string;
environment: string;
};
response: {
flatKeyValueObject: {[key: string]: string};
};
}

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/cloudly',
version: '4.3.11',
version: '5.0.6',
description: 'A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.'
}

View File

@@ -15,7 +15,7 @@ export const loginAction = loginStatePart.createAction<{ username: string; passw
async (statePartArg, payloadArg) => {
const currentState = statePartArg.getState();
const trLogin =
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>(
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.admin.IReq_Admin_LoginWithUsernameAndPassword>(
'/typedrequest',
'adminLoginWithUsernameAndPassword'
);
@@ -77,20 +77,34 @@ export const dataState = await appstate.getStatePart<IDataState>(
);
// Getting data
export const getAllDataAction = dataState.createAction(async (statePartArg, partialArg?: 'secrets' | 'images') => {
export const getAllDataAction = dataState.createAction(async (statePartArg) => {
let currentState = statePartArg.getState();
// Secrets
const trGetSecrets =
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
// SecretsGroups
const trGetSecretGroups =
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secretgroup.IReq_GetSecretGroups>(
'/typedrequest',
'adminGetConfigBundlesAndSecretGroups'
'getSecretGroups'
);
const response = await trGetSecrets.fire({
const response = await trGetSecretGroups.fire({
identity: loginStatePart.getState().identity,
});
currentState = {
...currentState,
...response,
secretGroups: response.secretGroups,
};
// SecretBundles
const trGetSecretBundles =
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secretbundle.IReq_GetSecretBundles>(
'/typedrequest',
'getSecretBundles'
);
const responseSecretBundles = await trGetSecretBundles.fire({
identity: loginStatePart.getState().identity,
});
currentState = {
...currentState,
secretBundles: responseSecretBundles.secretBundles,
};
// images
@@ -104,14 +118,14 @@ export const getAllDataAction = dataState.createAction(async (statePartArg, part
});
currentState = {
...currentState,
...responseImages,
images: responseImages.images,
};
// Clusters
const trGetClusters =
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.cluster.IRequest_GetAllClusters>(
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.cluster.IReq_Any_Cloudly_GetClusters>(
'/typedrequest',
'getAllClusters'
'getClusters'
);
const responseClusters = await trGetClusters.fire({
identity: loginStatePart.getState().identity,
@@ -119,7 +133,7 @@ export const getAllDataAction = dataState.createAction(async (statePartArg, part
currentState = {
...currentState,
...responseClusters,
clusters: responseClusters.clusters,
}
return currentState;
@@ -130,14 +144,13 @@ export const createSecretGroupAction = dataState.createAction(
async (statePartArg, payloadArg: plugins.interfaces.data.ISecretGroup) => {
let currentState = statePartArg.getState();
const trCreateSecretGroup =
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_CreateConfigBundlesAndSecretGroups>(
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secretgroup.IReq_CreateSecretGroup>(
'/typedrequest',
'adminCreateConfigBundlesAndSecretGroups'
'createSecretGroup'
);
const response = await trCreateSecretGroup.fire({
identity: loginStatePart.getState().identity,
secretBundles: [],
secretGroups: [payloadArg],
secretGroup: payloadArg,
});
currentState = await dataState.dispatchAction(getAllDataAction, null);
return currentState;
@@ -149,14 +162,13 @@ export const deleteSecretGroupAction = dataState.createAction(
async (statePartArg, payloadArg: { secretGroupId: string }) => {
let currentState = statePartArg.getState();
const trDeleteSecretGroup =
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_DeleteConfigBundlesAndSecretGroups>(
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secretgroup.IReq_DeleteSecretGroupById>(
'/typedrequest',
'adminDeleteConfigBundlesAndSecretGroups'
'deleteSecretGroupById'
);
const response = await trDeleteSecretGroup.fire({
identity: loginStatePart.getState().identity,
secretBundleIds: [],
secretGroupIds: [payloadArg.secretGroupId],
secretGroupId: payloadArg.secretGroupId,
});
currentState = await dataState.dispatchAction(getAllDataAction, null);
return currentState;
@@ -168,14 +180,13 @@ export const deleteSecretBundleAction = dataState.createAction(
async (statePartArg, payloadArg: { configBundleId: string }) => {
let currentState = statePartArg.getState();
const trDeleteConfigBundle =
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_DeleteConfigBundlesAndSecretGroups>(
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secretbundle.IReq_DeleteSecretBundleById>(
'/typedrequest',
'adminDeleteConfigBundlesAndSecretGroups'
'deleteSecretBundleById'
);
const response = await trDeleteConfigBundle.fire({
identity: loginStatePart.getState().identity,
secretBundleIds: [payloadArg.configBundleId],
secretGroupIds: [],
secretBundleId: payloadArg.configBundleId,
});
currentState = await dataState.dispatchAction(getAllDataAction, null);
return currentState;
@@ -249,7 +260,7 @@ export const addClusterAction = dataState.createAction(
currentState = {
...currentState,
...{
clusters: [...currentState.clusters, response.clusterConfig],
clusters: [...currentState.clusters, response.cluster],
},
}
return currentState;

View File

@@ -24,6 +24,7 @@ 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';
import { CloudlyViewExternalRegistries } from './cloudly-view-externalregistries.js';
declare global {
interface HTMLElementTagNameMap {
@@ -89,6 +90,10 @@ export class CloudlyDashboard extends DeesElement {
name: 'Clusters',
element: CloudlyViewClusters,
},
{
name: 'ExternalRegistries',
element: CloudlyViewExternalRegistries,
},
{
name: 'Images',
element: CloudlyViewImages,

View File

@@ -0,0 +1,129 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import {
DeesElement,
customElement,
html,
state,
css,
cssManager,
} from '@design.estate/dees-element';
import * as appstate from '../appstate.js';
@customElement('cloudly-view-externalregistries')
export class CloudlyViewExternalRegistries extends DeesElement {
@state()
private data: appstate.IDataState = {
secretGroups: [],
secretBundles: [],
};
constructor() {
super();
const subecription = appstate.dataState
.select((stateArg) => stateArg)
.subscribe((dataArg) => {
this.data = dataArg;
});
this.rxSubscriptions.push(subecription);
}
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
`,
];
public render() {
return html`
<cloudly-sectionheading>External Registries</cloudly-sectionheading>
<dees-table
.heading1=${'External Registries'}
.heading2=${'decoded in client'}
.data=${this.data.deployments}
.displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => {
return {
id: itemArg.id,
serverAmount: itemArg.data.servers.length,
};
}}
.dataActions=${[
{
name: 'add configBundle',
iconName: 'plus',
type: ['header', 'footer'],
actionFunc: async (dataActionArg) => {
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
heading: 'Add ConfigBundle',
content: html`
<dees-form>
<dees-input-text .key=${'id'} .label=${'ID'} .value=${''}></dees-input-text>
<dees-input-text
.key=${'data.secretGroupIds'}
.label=${'secretGroupIds'}
.value=${''}
></dees-input-text>
<dees-input-text
.key=${'data.includedTags'}
.label=${'includedTags'}
.value=${''}
></dees-input-text>
</dees-form>
`,
menuOptions: [
{ name: 'create', action: async (modalArg) => {} },
{
name: 'cancel',
action: async (modalArg) => {
modalArg.destroy();
},
},
],
});
},
},
{
name: 'delete',
iconName: 'trash',
type: ['contextmenu', 'inRow'],
actionFunc: async (actionDataArg) => {
plugins.deesCatalog.DeesModal.createAndShow({
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
content: html`
<div style="text-align:center">
Do you really want to delete the ConfigBundle?
</div>
<div
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
>
${actionDataArg.item.id}
</div>
`,
menuOptions: [
{
name: 'cancel',
action: async (modalArg) => {
await modalArg.destroy();
},
},
{
name: 'delete',
action: async (modalArg) => {
appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, {
configBundleId: actionDataArg.item.id,
});
await modalArg.destroy();
},
},
],
});
},
},
] as plugins.deesCatalog.ITableAction[]}
></dees-table>
`;
}
}

View File

@@ -18,11 +18,28 @@ export class CloudlySectionheading extends DeesElement {
public static styles = [
cssManager.defaultStyles,
css`
:host {
display: grid;
grid-template-columns: min-content min-content;
}
h1 {
font-family: 'Cal Sans';
letter-spacing: 0.025em;
margin: 0px;
margin-bottom: 16px;
white-space: nowrap;
}
.flag {
border-radius: 4px;
background: #8a0183;
height: 20px;
padding: 2px 4px;
margin-left: 16px;
font-size: 12px;
transform: translateY(12px);
white-space: nowrap;
}
`,
]
@@ -30,6 +47,7 @@ export class CloudlySectionheading extends DeesElement {
public render() {
return html`
<h1><slot></slot></h1>
<div class="flag">stability: alpha</div>
`;
}
}