Compare commits

..

150 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
b6bce76043 4.3.11
Some checks failed
Docker (tags) / security (push) Successful in 46s
Docker (tags) / test (push) Successful in 3m7s
Docker (tags) / release (push) Failing after 0s
Docker (tags) / metadata (push) Successful in 7s
2024-11-06 21:21:15 +01:00
cab57ab303 fix(overall): Refactor and improve code consistency across all modules 2024-11-06 21:21:14 +01:00
804f1f3b12 4.3.10
Some checks failed
Docker (tags) / security (push) Successful in 1m1s
Docker (tags) / test (push) Failing after 11s
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-11-06 21:16:12 +01:00
f0144fdd5b fix(dependencies): Updated dependencies and fixed Docker Alpine image retrieval issue in tests 2024-11-06 21:16:12 +01:00
81f286cb2f 4.3.9
Some checks failed
Docker (tags) / security (push) Successful in 58s
Docker (tags) / test (push) Failing after 3m6s
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-11-06 17:19:44 +01:00
1f12cb9f94 fix(test and dependencies): Corrected cloudlyUrl in test.apiclient and updated tapbundle dependency. 2024-11-06 17:19:43 +01:00
26490e8ddd 4.3.8
Some checks failed
Docker (tags) / security (push) Successful in 53s
Docker (tags) / test (push) Failing after 2m10s
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-11-06 16:59:18 +01:00
38d2120c35 fix(api client): Fixed localhost URL issue in test.client.ts 2024-11-06 16:59:17 +01:00
f80b8decbc 4.3.7
Some checks failed
Docker (tags) / security (push) Successful in 59s
Docker (tags) / test (push) Failing after 2m4s
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-11-06 16:53:22 +01:00
28cd6d1b49 fix(tests): Refactored test setup for consistency and isolated config initialization. 2024-11-06 16:53:21 +01:00
899e5b0a7d 4.3.6
Some checks failed
Docker (tags) / security (push) Successful in 55s
Docker (tags) / test (push) Failing after 2m8s
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-11-06 16:22:38 +01:00
0eff7c7510 fix(test): Enhance test helpers with dynamic Hetzner token retrieval. 2024-11-06 16:22:38 +01:00
7789348f4e 4.3.5
Some checks failed
Docker (tags) / security (push) Successful in 1m2s
Docker (tags) / test (push) Failing after 2m8s
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-11-06 16:13:55 +01:00
66a23a515b fix(helpers): Add missing sslMode configuration to Cloudly config. 2024-11-06 16:13:54 +01:00
7c1082f5a9 4.3.4
Some checks failed
Docker (tags) / security (push) Successful in 1m0s
Docker (tags) / test (push) Failing after 2m5s
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-11-06 03:56:46 +01:00
15ea5adec6 fix(testing): Fixed Cloudly testing setup and dependencies 2024-11-06 03:56:46 +01:00
da0dddcceb 4.3.3
Some checks failed
Docker (tags) / security (push) Successful in 1m0s
Docker (tags) / test (push) Failing after 1m37s
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-11-05 21:31:16 +01:00
b5433e412f fix(core): Fix configuration initialization by accepting a config argument 2024-11-05 21:31:15 +01:00
7eb6bf794c 4.3.2
Some checks failed
Docker (tags) / security (push) Successful in 42s
Docker (tags) / test (push) Failing after 1m46s
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-11-05 02:45:04 +01:00
b244518fcb fix(npmextra): Updated npm registry URL in npmextra.json 2024-11-05 02:45:04 +01:00
95d0396abb 4.3.1
Some checks failed
Docker (tags) / security (push) Successful in 58s
Docker (tags) / test (push) Failing after 3m59s
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-11-05 02:21:06 +01:00
a830299cc9 fix(package): Update dependency version for @git.zone/tspublish 2024-11-05 02:21:06 +01:00
10fc1d7fba 4.3.0
Some checks failed
Docker (tags) / security (push) Failing after 23s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-11-05 02:06:44 +01:00
614ed78928 feat(dependencies): Upgrade dependencies and include publish orders 2024-11-05 02:06:44 +01:00
ea7f6a6477 4.2.1
Some checks failed
Docker (tags) / security (push) Failing after 27s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-11-04 19:24:47 +01:00
2d746a9d1c fix(config): Fix Docker image URL in Gitea workflow. 2024-11-04 19:24:47 +01:00
a9fab24961 4.2.0
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-11-04 19:23:45 +01:00
5548d5a72d feat(cloudron): Add Dockerfile for Cloudron deployment 2024-11-04 19:23:45 +01:00
6b52e05a86 4.1.3
Some checks failed
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-28 21:54:55 +01:00
87e273c30e fix(dependency): Updated dependency @git.zone/tspublish to version ^1.6.0 2024-10-28 21:54:55 +01:00
0f3f4b8e3f 4.1.2
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-28 21:49:43 +01:00
5f16d8e494 fix(core): Corrected description and devDependencies 2024-10-28 21:49:43 +01:00
5148bd1fff 4.1.1
Some checks failed
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-28 16:55:25 +01:00
41c54a070c fix(core): Fixed syntax issues in commitinfo data and package.json file. 2024-10-28 16:55:25 +01:00
6956524c6e 4.1.0
Some checks failed
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-28 16:19:00 +01:00
7a1d933559 feat(core): Enhance core functionality for cloud management and orchestration 2024-10-28 16:19:00 +01:00
343acd4997 4.0.1
Some checks failed
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-28 15:58:26 +01:00
337d111cf6 fix(package_manager): Update @git.zone/tspublish dependency version 2024-10-28 15:58:26 +01:00
f49dce92cd 4.0.0
Some checks failed
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-28 01:39:49 +01:00
c6abfe69b8 BREAKING CHANGE(core): Significant overhaul with potential breaking changes, update to version 3.0.0 2024-10-28 01:39:49 +01:00
9d52c62335 1.2.5
Some checks failed
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-28 01:37:55 +01:00
971abd19c9 fix(build): Updated devDependencies for tspublish and removed buildDocs script 2024-10-28 01:37:55 +01:00
084a11d7ed 1.2.4
Some checks failed
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-27 19:50:39 +01:00
8f49f0cb4f fix(ci): Fix Docker images and npm registry URL in CI workflows 2024-10-27 19:50:39 +01:00
320b3ed9eb 1.2.3
Some checks failed
Docker (tags) / security (push) Failing after 12s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-23 20:09:24 +02:00
7cd6bc0e33 fix(cli): Set up CLI client definition and registry configuration 2024-10-23 20:09:24 +02:00
a218805f27 1.2.2
Some checks failed
Docker (tags) / security (push) Failing after 16s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-23 20:06:16 +02:00
6f56304e07 fix(docs): Updated documentation with clearer usage instructions and examples. 2024-10-23 20:06:16 +02:00
4eb62200e8 1.2.1
Some checks failed
Docker (tags) / security (push) Failing after 17s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-23 15:59:49 +02:00
c105596455 fix(core): Fixed startup issue for the Cloudly instance 2024-10-23 15:59:49 +02:00
5aa136b8d9 1.2.0
Some checks failed
Docker (tags) / security (push) Failing after 16s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-21 16:37:46 +02:00
240516520a feat(cli): Add tspublish.json for CLI client and interfaces 2024-10-21 16:37:46 +02:00
4e1f9464fe 1.1.9
Some checks failed
Docker (tags) / security (push) Failing after 18s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-21 14:23:43 +02:00
41f9f93d1c fix(build): Update Node types and other dependencies, add tspublish.json for api client 2024-10-21 14:23:43 +02:00
c502d410b1 1.1.8
Some checks failed
Docker (tags) / security (push) Failing after 19s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-16 14:35:38 +02:00
53f96095c7 fix(big fix upgrade): upgrade multiple areas of the core functionalities 2024-10-16 14:35:38 +02:00
d212dfb9f9 1.1.7
Some checks failed
Docker (tags) / security (push) Failing after 18s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-08-25 14:29:26 +02:00
0ec665516d fix(deps): Update dependencies to latest versions 2024-08-25 14:29:26 +02:00
acc642adf9 1.1.6
Some checks failed
Docker (tags) / security (push) Failing after 16s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-06-20 19:20:17 +02:00
a6521708f7 fix(core): update 2024-06-20 19:20:16 +02:00
206fe445bc 1.1.5
Some checks failed
Docker (tags) / security (push) Failing after 18s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-06-20 19:00:58 +02:00
a7ee92cde9 fix(core): update 2024-06-20 19:00:58 +02:00
cdbab26008 1.1.4
Some checks failed
Docker (tags) / security (push) Failing after 19s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-06-13 10:07:54 +02:00
1983c64b77 fix(core): update 2024-06-13 10:07:53 +02:00
a6e3a7f5fe prepare service management 2024-06-13 09:36:02 +02:00
6dd687012f 1.1.3
Some checks failed
Docker (tags) / security (push) Failing after 15s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-06-05 14:13:04 +02:00
55b2872ffc fix(structure): improve structure, prepare better CI integration 2024-06-05 14:13:03 +02:00
2e6e7f6ca8 1.1.2
Some checks failed
Docker (tags) / security (push) Failing after 14s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-06-02 21:39:32 +02:00
f453ce3126 fix(imagemanager): prepare proper storage and retrieval of container images 2024-06-02 21:39:31 +02:00
b8dd84b8a6 1.1.1
Some checks failed
Docker (tags) / security (push) Failing after 14s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-06-01 05:48:57 +02:00
338ed5ed75 fix(image registry): start work on image registry 2024-06-01 05:48:57 +02:00
482a6a101c 1.1.0
Some checks failed
Docker (tags) / security (push) Failing after 15s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-05-30 22:49:39 +02:00
929f250006 feat(gaurds): use better smartguards to verify action authorization 2024-05-30 22:49:39 +02:00
6b3fd2ce31 1.0.216 2024-05-28 18:45:34 +02:00
21961fa2d7 fix(switch to unified package for cloudly + api + cli): update 2024-05-28 18:45:34 +02:00
149 changed files with 17745 additions and 7582 deletions

View File

@@ -6,8 +6,8 @@ on:
- '**'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
IMAGE: code.foss.global/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
@@ -27,7 +27,7 @@ jobs:
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Audit production dependencies
@@ -54,7 +54,7 @@ jobs:
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Test stable

View File

@@ -6,13 +6,14 @@ on:
- '*'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_LOGIN_DOCKER_GITEA: ${{ github.server_url }}|${{ gitea.repository_owner }}|${{ secrets.GITEA_TOKEN }}
# NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
# NPMCI_LOGIN_DOCKER_GITEA: ${{ github.server_url }}|${{ gitea.repository_owner }}|${{ secrets.GITEA_TOKEN }}
NPMCI_LOGIN_DOCKER_DOCKERREGISTRY: ${{ secrets.NPMCI_LOGIN_DOCKER_DOCKERREGISTRY }}
NPMCI_SECRET01: ${{ secrets.NPMCI_SECRET01 }}
jobs:
security:
@@ -27,7 +28,7 @@ jobs:
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Audit production dependencies
@@ -54,7 +55,7 @@ jobs:
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
pnpm install -g @ship.zone/npmci
npmci npm prepare
- name: Test stable
@@ -74,7 +75,7 @@ jobs:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: registry.gitlab.com/hosttoday/ht-docker-dbase:npmci
image: code.foss.global/host.today/ht-docker-dbase:npmci
steps:
- uses: actions/checkout@v3
@@ -82,15 +83,14 @@ jobs:
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
pnpm install -g @ship.zone/npmci
- name: Release
run: |
npmci docker login
npmci docker build
npmci docker test
# npmci docker push gitea.lossless.digital
npmci docker push dockerregistry.lossless.digital
npmci docker push code.foss.global
metadata:
needs: test

View File

@@ -1,6 +1,6 @@
# gitzone dockerfile_service
## STAGE 1 // BUILD
FROM registry.gitlab.com/hosttoday/ht-docker-node:npmci as node1
FROM code.foss.global/host.today/ht-docker-node:npmci as node1
COPY ./ /app
WORKDIR /app
ARG NPMCI_TOKEN_NPM2
@@ -12,7 +12,7 @@ RUN pnpm run build
# gitzone dockerfile_service
## STAGE 2 // install production
FROM registry.gitlab.com/hosttoday/ht-docker-node:npmci as node2
FROM code.foss.global/host.today/ht-docker-node:npmci as node2
WORKDIR /app
COPY --from=node1 /app /app
RUN rm -rf .pnpm-store
@@ -24,7 +24,7 @@ RUN rm -rf node_modules/ && pnpm install --prod
## STAGE 3 // rebuild dependencies for alpine
FROM registry.gitlab.com/hosttoday/ht-docker-node:alpinenpmci as node3
FROM code.foss.global/host.today/ht-docker-node:alpine_npmci as node3
WORKDIR /app
COPY --from=node2 /app /app
ARG NPMCI_TOKEN_NPM2
@@ -34,7 +34,7 @@ RUN pnpm config set store-dir .pnpm-store
RUN pnpm rebuild -r
## STAGE 4 // the final production image with all dependencies in place
FROM registry.gitlab.com/hosttoday/ht-docker-node:alpine as node4
FROM code.foss.global/host.today/ht-docker-node:alpine as node4
WORKDIR /app
COPY --from=node3 /app /app

View File

@@ -0,0 +1 @@
FROM serve.zone/cloudly:latest

475
changelog.md Normal file
View File

@@ -0,0 +1,475 @@
# 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
- Updated cloud configuration management for better reliability.
- Enhanced security measures in the authentication and authorization processes.
- Streamlined deployment logic in cluster management.
- Refactored code to improve maintainability and readability.
## 2024-11-06 - 4.3.10 - fix(dependencies)
Updated dependencies and fixed Docker Alpine image retrieval issue in tests
- Updated @push.rocks/tapbundle to version ^5.5.0 in devDependencies.
- Updated @push.rocks/smartrequest to version ^2.0.23 in dependencies.
- Ensured the Docker Alpine image is retrieved as a local tarball in cloudlyfactory.ts test helper.
## 2024-11-06 - 4.3.9 - fix(test and dependencies)
Corrected cloudlyUrl in test.apiclient and updated tapbundle dependency.
- Fixed cloudlyUrl assignment in the test.apiclient to use the correct helper method.
- Updated tapbundle dependency version from ^5.4.3 to ^5.4.4.
## 2024-11-06 - 4.3.8 - fix(api client)
Fixed localhost URL issue in test.client.ts
- Changed the cloudlyUrl in test.client.ts from 'localhost' to '127.0.0.1' to ensure consistency in network requests.
## 2024-11-06 - 4.3.7 - fix(tests)
Refactored test setup for consistency and isolated config initialization.
- test/helpers/cloudlyfactory.ts: Test configuration setup was refactored to ensure consistent initialization of cloudly configuration across tests.
- test/test.apiclient.ts: Updated cloudlyApiClient test setup to use testCloudlyConfig for dynamic port allocation.
## 2024-11-06 - 4.3.6 - fix(test)
Enhance test helpers with dynamic Hetzner token retrieval.
- Updated test/helpers/cloudlyfactory.ts to retrieve Hetzner token from environment variables.
- Expanded docker_tags workflow to securely handle and pass new environment secrets.
## 2024-11-06 - 4.3.5 - fix(helpers)
Add missing sslMode configuration to Cloudly config.
- Added the sslMode key with a value of 'none' to the Cloudly configuration object in test/helpers/cloudlyfactory.ts.
## 2024-11-06 - 4.3.4 - fix(testing)
Fixed Cloudly testing setup and dependencies
- Updated devDependency @push.rocks/tapbundle version from ^5.3.0 to ^5.4.3 in package.json.
- Updated devDependency @push.rocks/npmextra version from ^5.1.1 to ^5.1.2 in package.json.
- Improved the Cloudly test suite setup to ensure proper initialization of MongoDB and S3 services.
- Ensured asynchronous stopping and cleanup of MongoDB and S3 services post-test execution.
## 2024-11-05 - 4.3.3 - fix(core)
Fix configuration initialization by accepting a config argument
- Configuration initialization now accepts an optional config argument
- Updated test cloudly factory to use default public URL and port
- Updated dependencies versions
## 2024-11-05 - 4.3.2 - fix(npmextra)
Updated npm registry URL in npmextra.json
## 2024-11-05 - 4.3.1 - fix(package)
Update dependency version for @git.zone/tspublish
- Bump @git.zone/tspublish from version ^1.7.6 to ^1.7.7 in package.json
## 2024-11-05 - 4.3.0 - feat(dependencies)
Upgrade dependencies and include publish orders
- Upgraded @git.zone/tsbuild to version ^2.2.0
- Upgraded @git.zone/tspublish to version ^1.7.6
- Upgraded @types/node to version ^22.8.7
- Added publish order to ts_apiclient/tspublish.json and ts_interfaces/tspublish.json
## 2024-11-04 - 4.2.1 - fix(config)
Fix Docker image URL in Gitea workflow.
- Corrected the IMAGE URL from 'hosttoday' to 'host.today'.
## 2024-11-04 - 4.2.0 - feat(cloudron)
Add Dockerfile for Cloudron deployment
- Introduced a new Dockerfile for Cloudron deployment.
- The Dockerfile uses the latest version of cloudly as a base image.
## 2024-10-28 - 4.1.3 - fix(dependency)
Updated dependency @git.zone/tspublish to version ^1.6.0
- Bumped @git.zone/tspublish version from ^1.5.5 to ^1.6.0 in package.json
## 2024-10-28 - 4.1.2 - fix(core)
Corrected description and devDependencies
- Updated package.json description to accurately reflect features.
- Added `@git.zone/tsdoc` to devDependencies.
- Corrected version discrepancy in `@types/node` devDependency.
- Standardized description across multiple files including npmextra.json.
## 2024-10-28 - 4.1.1 - fix(core)
Fixed syntax issues in commitinfo data and package.json file.
- Added a missing newline at the end of the package.json file.
- Corrected a trailing comma and added proper syntax in the commitinfo data.
## 2024-10-28 - 4.1.0 - feat(core)
Enhance core functionality for cloud management and orchestration
- Improved initialization and management of cloud environments with Docker Swarmkit.
- Added capability to manage DNS records via Cloudflare.
- Introduced integration support for DigitalOcean resources.
## 2024-10-28 - 4.0.1 - fix(package_manager)
Update @git.zone/tspublish dependency version
- Bump @git.zone/tspublish version from 1.5.4 to 1.5.5.
## 2024-10-28 - 4.0.0 - BREAKING CHANGE(core)
Significant overhaul with potential breaking changes, update to version 3.0.0
- Updated project version from 1.2.5 to 3.0.0 in package.json
## 2024-10-28 - 1.2.5 - fix(build)
Updated devDependencies for tspublish and removed buildDocs script
- devDependencies updated: @git.zone/tspublish to version ^1.5.4
- Removed: buildDocs script from the scripts section
## 2024-10-27 - 1.2.4 - fix(ci)
Fix Docker images and npm registry URL in CI workflows
- Updated Docker image registry URL from 'registry.gitlab.com' to 'code.foss.global'.
- Fixed npmci package installation path from '@shipzone/npmci' to '@ship.zone/npmci'.
## 2024-10-23 - 1.2.3 - fix(cli)
Set up CLI client definition and registry configuration
- Added a console log message for CLI identification.
- Defined package name for CLI client.
- Configured npm registries for package distribution.
## 2024-10-23 - 1.2.2 - fix(docs)
Updated documentation with clearer usage instructions and examples.
- Refined setup guides in README.
- Added more detailed examples in code snippets.
- Clarified cloud service integration instructions.
## 2024-10-23 - 1.2.1 - fix(core)
Fixed startup issue for the Cloudly instance
## 2024-10-21 - 1.2.0 - feat(cli)
Add tspublish.json for CLI client and interfaces
- Added registration details for publishing CLI client (@serve.zone/cli)
- Specified npm and custom registries in the tspublish.json for better publish control
- Updated dependency version for @git.zone/tspublish in package.json
## 2024-10-21 - 1.1.9 - fix(build)
Update Node types and other dependencies, add tspublish.json for api client
- Updated Node types devDependency to version ^22.7.7 from ^22.7.5.
- Updated smartbucket dependency to version ^3.0.23 from ^3.0.22.
- Added tspublish configuration file to the api client.
- Fixed the npm publish script in package.json.
## 2024-10-16 - 1.1.8 - fix(big fix upgrade)
fix: update dependency versions and address type errors
- Updated all listed dependencies in the package.json to their specified ranges.
- Fixed type mismatches and added missing imports in various TypeScript files.
- Refined existing tests and added a new helper to manage Docker image streams.
## 2024-08-25 - 1.1.7 - fix(deps)
Update dependencies to latest versions
- Updated @git.zone/tsbuild from ^2.1.80 to ^2.1.84
- Updated @push.rocks/tapbundle from ^5.0.23 to ^5.0.24
- Updated @types/node from ^20.14.6 to ^22.5.0
- Updated @apiclient.xyz/docker from ^1.2.2 to ^1.2.3
- Updated @design.estate/dees-catalog from ^1.0.289 to ^1.1.6
- Updated @design.estate/dees-element from ^2.0.34 to ^2.0.36
- Updated @git.zone/tsrun from ^1.2.37 to ^1.2.49
- Updated @push.rocks/smartbucket from ^3.0.20 to ^3.0.22
- Updated @push.rocks/smartpromise from ^4.0.3 to ^4.0.4
- Updated @serve.zone/interfaces from ^1.0.74 to ^1.0.78
- Updated @tsclass/tsclass from ^4.0.60 to ^4.1.2
## 2024-06-20 - 1.1.6 - Updates
Routine updates and fixes.
- (fix) core: update
## 2024-06-13 - 1.1.4 - Service Management Preparation
Incorporated updates and service management preparations.
- (fix) core: update
- (feat) prepare service management
## 2024-06-05 - 1.1.3 - CI Integration Improvement
Structural improvements and better CI integration preparation.
- (fix) structure: improve structure, prepare better CI integration
## 2024-06-02 - 1.1.2 - Image Manager Update
Prepared proper storage and retrieval of container images.
- (fix) imagemanager: prepare proper storage and retrieval of container images
## 2024-06-01 - 1.1.0 - Image Registry Work
Initiated work on image registry.
- (fix) image registry: start work on image registry
## 2024-05-30 - 1.0.216 - Enhanced Smartguards
Enhanced smartguards to verify action authorization.
- (feat) guards: use better smartguards to verify action authorization
## 2024-05-28 - 1.0.215 - Unified Package Update
Updated package unification for cloudly + API + CLI.
- (fix) switch to unified package for cloudly + API + CLI: update
## 2024-05-05 - 1.0.214 - Core Updates
Routine core updates.
- (fix) core: update
## 2024-04-20 - 1.0.213 - Core Update
Routine core updates.
- (fix) core: update

19
license Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2014 Task Venture Capital GmbH (hello@task.vc)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -2,35 +2,48 @@
"npmci": {
"npmGlobalTools": [],
"npmAccessLevel": "public",
"npmRegistryUrl": "verdaccio.lossless.one",
"npmRegistryUrl": "verdaccio.lossless.digital",
"dockerRegistryRepoMap": {
"registry.gitlab.com": "losslessone/services/servezone/cloudly"
"code.foss.global": "serve.zone/cloudly"
},
"dockerBuildargEnvMap": {
"NPMCI_TOKEN_NPM2": "NPMCI_TOKEN_NPM2"
}
"dockerBuildargEnvMap": {}
},
"gitzone": {
"projectType": "service",
"module": {
"githost": "gitlab.com",
"gitscope": "servezone/private",
"githost": "code.foss.global",
"gitscope": "serve.zone",
"gitrepo": "cloudly",
"description": "A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.",
"description": "A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.",
"npmPackagename": "@serve.zone/cloudly",
"license": "UNLICENSED",
"license": "MIT",
"keywords": [
"cloud",
"multi-cloud management",
"Docker Swarmkit",
"container orchestration",
"cloud services",
"API integration",
"web interface",
"CLI",
"CI/CD integration",
"cloud providers",
"DigitalOcean",
"Hetzner Cloud",
"Cloudflare",
"container management",
"TypeScript",
"Node.js",
"infrastructure automation",
"devOps",
"secret management",
"configuration management",
"LetsEncrypt SSL",
"cloud infrastructure automation",
"node.js",
"TypeScript"
"task scheduling",
"logging",
"SSL management",
"system logging",
"cloud API client",
"frontend",
"backend",
"security"
]
}
},

View File

@@ -1,67 +1,79 @@
{
"name": "@serve.zone/cloudly",
"version": "1.0.215",
"version": "5.0.6",
"private": false,
"description": "A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
"description": "A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.",
"type": "module",
"exports": {
".": "./dist/index.js",
"./apiclient": "./dist_apiclient/index.js",
"./cliclient": "./dist_cliclient/index.js",
"./web": "./dist_web/index.js"
},
"author": "Task Venture Capital GmbH",
"license": "UNLICENSED",
"license": "MIT",
"scripts": {
"test": "(tstest test/)",
"build": "tsbuild --web --allowimplicitany && tsbundle website --production",
"test": "(tstest test/ --verbose --logfile --timeout 120)",
"build": "tsbuild tsfolders --web --allowimplicitany && tsbundle website --production",
"start": "node cli.js",
"startTs": "node cli.ts.js",
"watch": "tswatch website",
"localPublish": "gitzone commit"
"publish": "tspublish",
"docs": "tsdoc aidoc"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.1.65",
"@git.zone/tsbundle": "^2.0.15",
"@git.zone/tstest": "^1.0.90",
"@git.zone/tswatch": "^2.0.23",
"@push.rocks/tapbundle": "^5.0.23",
"@types/node": "^20.12.8"
"@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.0.23",
"@api.global/typedserver": "^3.0.29",
"@api.global/typedrequest": "3.1.10",
"@api.global/typedrequest-interfaces": "^3.0.19",
"@api.global/typedserver": "^3.0.77",
"@api.global/typedsocket": "^3.0.1",
"@apiclient.xyz/cloudflare": "^6.0.1",
"@apiclient.xyz/digitalocean": "^1.0.5",
"@apiclient.xyz/hetznercloud": "^1.0.18",
"@apiclient.xyz/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.0.289",
"@design.estate/dees-domtools": "^2.0.57",
"@design.estate/dees-element": "^2.0.34",
"@git.zone/tsrun": "^1.2.37",
"@design.estate/dees-catalog": "^1.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.0.13",
"@push.rocks/npmextra": "^5.3.3",
"@push.rocks/projectinfo": "^5.0.1",
"@push.rocks/qenv": "^6.0.5",
"@push.rocks/smartacme": "^4.0.8",
"@push.rocks/smartbucket": "^2.0.4",
"@push.rocks/smartcli": "^4.0.10",
"@push.rocks/smartdata": "^5.2.1",
"@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.16.4",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartexit": "^1.0.23",
"@push.rocks/smartfile": "^11.0.14",
"@push.rocks/smartguard": "^2.0.1",
"@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.0.4",
"@push.rocks/smartlog": "^3.0.1",
"@push.rocks/smartlog-destination-clickhouse": "^1.0.11",
"@push.rocks/smartpath": "^5.0.18",
"@push.rocks/smartpromise": "^4.0.3",
"@push.rocks/smartrequest": "^2.0.22",
"@push.rocks/smartjwt": "^2.2.1",
"@push.rocks/smartlog": "^3.1.8",
"@push.rocks/smartlog-destination-clickhouse": "^1.0.13",
"@push.rocks/smartlog-interfaces": "^3.0.2",
"@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.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",
"@serve.zone/interfaces": "^1.0.51",
"@tsclass/tsclass": "^4.0.54"
"@tsclass/tsclass": "^9.2.0"
},
"files": [
"ts/**/*",
@@ -87,16 +99,32 @@
},
"homepage": "https://gitlab.com/servezone/private/cloudly#readme",
"keywords": [
"cloud",
"multi-cloud management",
"Docker Swarmkit",
"container orchestration",
"cloud services",
"API integration",
"web interface",
"CLI",
"CI/CD integration",
"cloud providers",
"DigitalOcean",
"Hetzner Cloud",
"Cloudflare",
"container management",
"TypeScript",
"Node.js",
"infrastructure automation",
"devOps",
"secret management",
"configuration management",
"LetsEncrypt SSL",
"cloud infrastructure automation",
"node.js",
"TypeScript"
]
"task scheduling",
"logging",
"SSL management",
"system logging",
"cloud API client",
"frontend",
"backend",
"security"
],
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
}

18311
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

@@ -0,0 +1,19 @@
- This repository contains 4 projects around serve.zone
- the cloudly backend under ts/*
- the cloudly frontend under ts_web/*
- the api client under ts_apiclient
- the cli client under ts_cliclient
- the easiest method to spawn up a cloudly instance is to use the docker image:
`code.foss.global/serve.zone/cloudly:latest`
- Note: the exports are defined in the package.json.
- For now, cloud wise only the setup with cloudron and hetzner cloud is supported.
## 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

262
readme.md
View File

@@ -1,88 +1,242 @@
# @serve.zone/cloudly
configure the cloud
# @serve.zone/cloudly 🚀
**Multi-cloud orchestration made simple.** Manage containerized applications across cloud providers with Docker Swarmkit, featuring web dashboards, CLI tools, and powerful APIs.
## 🎯 What is Cloudly?
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
## Install
To install `@serve.zone/cloudly`, run the following command in your terminal:
```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 help you manage and configure cloud environments. This package provides a comprehensive TypeScript and ESM-based interface for interacting with various cloud services, including Docker Swarmkit cluster management, and integration with cloud providers such as DigitalOcean, Hetzner Cloud, and Cloudflare.
### Getting Started
Before diving into the specifics, ensure your environment is properly set up. This includes having Node.js installed (preferably the latest LTS version), and if you are working in a TypeScript project, ensure TypeScript is configured.
#### Initializing Cloudly
First, import `Cloudly` class from the package and initialize it as shown below:
### Basic Setup
```typescript
import { Cloudly } from '@serve.zone/cloudly';
const myCloudlyInstance = new Cloudly();
// 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,
}
});
// Start the platform
await cloudly.start();
console.log('🎉 Cloudly is running!');
```
The `Cloudly` class is the entry point to using the library features. It prepares the environment for configuring the cloud services.
## 🏗️ Architecture
#### Configuration
Configuration plays a pivotal role in how `@serve.zone/cloudly` operates. The library expects certain configurations to be provided, which can include credentials for cloud services, database connections, etc.
Cloudly follows a modular architecture with clear separation of concerns:
For example, to configure a connection to MongoDB, specify your MongoDB details as shown:
```
┌─────────────────────────────────────────────┐
│ 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
const myCloudlyConfig = {
mongoDescriptor: {
mongoDbUrl: 'mongodb+srv://<username>:<password>@<cluster>.mongodb.net/myFirstDatabase',
mongoDbName: 'myDatabase',
mongoDbUser: 'myUser',
mongoDbPass: 'myPassword',
},
// Additional configuration values...
};
// Create a new cluster
const cluster = await cloudly.clusterManager.createCluster({
name: 'production-cluster',
region: 'eu-central',
nodeCount: 3
});
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
// Deploy a service
await cluster.deployService({
name: 'api-service',
image: 'myapp:latest',
replicas: 3,
ports: [{ published: 80, target: 3000 }]
});
```
#### Managing Docker Swarmkit Cluster
Cloudly allows managing Docker Swarmkit clusters through an abstracted interface, simplifying operations such as deployment and scaling.
### Secret Management
```typescript
// Assuming myCloudlyInstance is already configured and initialized
// 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 }
]
});
// Start the cloud instance
await myCloudlyInstance.start();
// Now you can perform various operations on your Docker Swarmkit cluster
// Create a bundle for deployment
const bundle = await cloudly.secretManager.createSecretBundle({
name: 'production-secrets',
secretGroups: [secretGroup]
});
```
### Integration with Cloud Providers
`@serve.zone/cloudly` integrates seamlessly with cloud providers like DigitalOcean, Hetzner Cloud, etc., by leveraging the power of APIs provided by these platforms.
#### Managing DigitalOcean Resources
To manage DigitalOcean resources, you'll need to configure your DigitalOcean token and then use the provided interfaces to interact with the resources, such as creating droplets, managing volumes, etc.
### DNS Management
```typescript
// Set your DigitalOcean API token
const digitalOceanToken = "your_digital_ocean_api_token";
// Now you can use myCloudlyInstance to manage DigitalOcean resources
// Create DNS records via Cloudflare
const record = await cloudly.cloudflareConnector.createDNSRecord(
'example.com',
'api.example.com',
'A',
'192.168.1.1'
);
```
#### Using the Cloudflare Integration
Similarly, for managing DNS records and SSL certificates with Cloudflare, set up your Cloudflare API token:
### Web Dashboard
```typescript
const cloudflareToken = "your_cloudflare_api_token";
import { html } from '@design.estate/dees-element';
// Use myCloudlyInstance to interact with Cloudflare, such as setting DNS records
// 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(dashboard);
```
### Advanced Usage
`@serve.zone/cloudly` offers more than just cloud resource management. It integrates various modules for error logging, security, working with JSON data, and much more. Explore the comprehensive documentation and typings to leverage the full potential of the package.
## 🛠️ CLI Usage
### Conclusion
With `@serve.zone/cloudly`, configuring the cloud becomes a less tedious task. By abstracting away the complexities and providing a unified interface to manage cloud resources, development efficiency is significantly improved. The examples provided above merely scratch the surface of what's possible. Dive into the detailed documentation to explore all features and capabilities.
The CLI provides quick access to all Cloudly features:
```bash
# Login to your Cloudly instance
servezone login --url https://cloudly.example.com
# List clusters
servezone clusters list
# Deploy a service
servezone deploy --cluster prod --image myapp:latest
# Manage secrets
servezone secrets create --name api-key --value "secret123"
# View logs
servezone logs --service api-service --follow
```
## 📦 Package Exports
This monorepo publishes multiple packages:
- **@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
## 🔒 Security Features
- **End-to-end encryption** for secrets
- **Role-based access control** (RBAC)
- **Automatic SSL/TLS** certificate management
- **Secure token-based authentication**
- **Audit logging** for compliance
## 🚢 Production Ready
Cloudly is battle-tested in production environments managing:
- High-traffic web applications
- Microservice architectures
- CI/CD pipelines
- Data processing workloads
- Real-time communication systems
## 🤝 Development
```bash
# Clone the repository
git clone https://gitlab.com/servezone/private/cloudly.git
# Install dependencies
pnpm install
# Run tests
pnpm test
# Build the project
pnpm build
# Start development mode
pnpm watch
```
## 📖 Documentation
For detailed documentation, API references, and guides, visit our [documentation site](https://cloudly.serve.zone).
## License and Legal Information
@@ -101,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

@@ -0,0 +1,52 @@
import { Qenv } from '@push.rocks/qenv';
const testQenv = new Qenv('./', './.nogit/');
import * as cloudly from '../../ts/index.js';
const stopFunctions: Array<() => Promise<void>> = [];
const tapToolsNodeMod = await import('@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_bucket');
stopFunctions.push(async () => {
await smarts3.stop();
});
export const testCloudlyConfig: cloudly.ICloudlyConfig = {
cfToken: await testQenv.getEnvVarOnDemand('CF_TOKEN'),
environment: 'integration',
letsEncryptEmail: 'test@serve.zone',
publicUrl: '127.0.0.1',
publicPort: '8080',
mongoDescriptor: await smartmongo.getMongoDescriptor(),
s3Descriptor: await smarts3.getS3Descriptor({
bucketName: 'cloudly_test_bucket'
}),
sslMode: 'none',
...(() => {
if (process.env.NPMCI_SECRET01) {
return {
hetznerToken: process.env.NPMCI_SECRET01,
};
}
})(),
};
await tapToolsNodeMod.tapNodeTools.testFileProvider.getDockerAlpineImageAsLocalTarball();
export const createCloudly = async () => {
const cloudlyInstance = new cloudly.Cloudly(testCloudlyConfig);
return cloudlyInstance;
};
export const stopCloudly = async () => {
await Promise.all(stopFunctions.map((stopFunction) => stopFunction()));
};
export const getEnvVarOnDemand = async (envVarName: string) => {
return testQenv.getEnvVarOnDemand(envVarName);
};

9
test/helpers/docker.ts Normal file
View File

@@ -0,0 +1,9 @@
import * as smartstream from '@push.rocks/smartstream';
import * as smartpath from '@push.rocks/smartpath';
export const getAlpineImageReadableStream = async () => {
const currentDir = smartpath.get.dirnameFromImportMetaUrl(import.meta.url);
const imagePath = smartpath.join(currentDir, '../../.nogit/testfiles/alpine.tar');
const readableStream = smartstream.nodewebhelpers.createWebReadableStreamFromFile(imagePath);
return readableStream;
}

2
test/helpers/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from './cloudlyfactory.js';
export * from './docker.js';

138
test/test.apiclient.ts Normal file
View File

@@ -0,0 +1,138 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as helpers from './helpers/index.js';
import * as cloudly from '../ts/index.js';
import * as cloudlyApiClient from '../ts_apiclient/index.js';
import { Image } from '../ts_apiclient/classes.image.js';
let testCloudly: cloudly.Cloudly;
let testClient: cloudlyApiClient.CloudlyApiClient;
tap.preTask('should start cloudly', async () => {
testCloudly = await helpers.createCloudly();
await testCloudly.start();
});
tap.preTask('should create a new machine user for testing', async () => {
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',
password: 'test',
tokens: [{
token: 'test',
expiresAt: Date.now() + 3600 * 1000 * 24 * 365,
assignedRoles: ['admin'],
}],
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('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 () => {
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 () => {
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 () => {
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) => {
await toolsArg.delayFor(10000);
await helpers.stopCloudly();
await testClient.stop();
await testCloudly.stop();
})
export default tap.start();

View File

@@ -1,29 +1,11 @@
import { expect, tap } from '@push.rocks/tapbundle';
import { Qenv } from '@push.rocks/qenv';
const testQenv = new Qenv('./', './.nogit/');
process.env.TESTING_CLOUDLY = 'true';
delete process.env.CLI_CALL;
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as helpers from './helpers/index.js';
import * as cloudly from '../ts/index.js';
let testCloudly: cloudly.Cloudly;
tap.test('first test', async () => {
const cloudlyConfig: cloudly.ICloudlyConfig = {
cfToken: testQenv.getEnvVarOnDemand('CF_TOKEN'),
environment: 'integration',
letsEncryptEmail: testQenv.getEnvVarOnDemand('LETSENCRYPT_EMAIL'),
publicUrl: testQenv.getEnvVarOnDemand('SERVEZONE_URL'),
publicPort: testQenv.getEnvVarOnDemand('SERVEZONE_PORT'),
mongoDescriptor: {
mongoDbName: testQenv.getEnvVarOnDemand('MONGODB_DATABASE'),
mongoDbUser: testQenv.getEnvVarOnDemand('MONGODB_USER'),
mongoDbPass: testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'),
mongoDbUrl: testQenv.getEnvVarOnDemand('MONGODB_URL'),
},
digitalOceanToken: testQenv.getEnvVarOnDemand('DIGITALOCEAN_TOKEN'),
};
testCloudly = new cloudly.Cloudly(cloudlyConfig);
testCloudly = await helpers.createCloudly();
expect(testCloudly).toBeInstanceOf(cloudly.Cloudly);
});
@@ -32,8 +14,10 @@ tap.test('should init cloudly', async () => {
});
tap.test('should end the service', async (tools) => {
await tools.delayFor(5000);
await helpers.stopCloudly();
await testCloudly.stop();
tools.delayFor(1000).then(() => process.exit())
tools.delayFor(1000).then(() => process.exit());
});
tap.start();

View File

@@ -1,8 +1,8 @@
/**
* autocreated commitinfo by @pushrocks/commitinfo
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
name: '@serve.zone/cloudly',
version: '1.0.215',
description: 'A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.'
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

@@ -0,0 +1,13 @@
import * as plugins from '../plugins.js';
export const demoImages: plugins.servezoneInterfaces.data.IImage[] = [
{
id: 'DemoImage1',
data: {
name: 'DemoImage1',
description: 'DemoImage1',
versions: [],
}
}
];

View File

@@ -1,4 +1,4 @@
import * as plugins from '../cloudly.plugins.js';
import * as plugins from '../plugins.js';
// Create an array to hold 10 ISecretGroup objects
const demoSecretGroups: plugins.servezoneInterfaces.data.ISecretGroup[] = [];
@@ -63,6 +63,8 @@ for (let i = 0; i < demoSecretGroups.length; i++) {
id: `configBundleId${i + 1}`,
data: {
name: `Demo Config Bundle ${i + 1}`,
imageClaims: [],
type: 'external',
description: 'Demo Purpose',
includedSecretGroupIds: [secretGroup.id],
includedTags: secretGroup.data.tags,

View File

@@ -0,0 +1,20 @@
import * as plugins from '../plugins.js';
import * as paths from '../paths.js';
import type { Cloudly } from '../classes.cloudly.js';
export const getUsers = async (cloudlyRef: Cloudly) => {
const users: plugins.servezoneInterfaces.data.IUser[] = [];
const envAdminUser = await cloudlyRef.config.appData.waitForAndGetKey('servezoneAdminaccount');
if (envAdminUser) {
users.push({
id: 'envadmin',
data: {
type: 'human',
username: envAdminUser.split(':')[0],
password: envAdminUser.split(':')[1],
role: 'admin',
},
});
}
return users;
};

View File

@@ -1,4 +1,4 @@
import type { Cloudly } from '../cloudly.classes.cloudly.js';
import type { Cloudly } from '../classes.cloudly.js';
export const installDemoData = async (cloudlyRef: Cloudly) => {
@@ -35,4 +35,31 @@ export const installDemoData = async (cloudlyRef: Cloudly) => {
await cluster.delete();
}
// ================================================================================
// USERS
const users = await cloudlyRef.authManager.CUser.getInstances({});
for (const user of users) {
await user.delete();
}
const demoDataUsers = await import('./demo.data.users.js');
for (const user of await demoDataUsers.getUsers(cloudlyRef)) {
const userInstance = new cloudlyRef.authManager.CUser();
Object.assign(userInstance, user);
await userInstance.save();
}
// ================================================================================
// IMAGES
const images = await cloudlyRef.imageManager.CImage.getInstances({});
for (const image of images) {
await image.delete();
}
const demoDataImages = await import('./demo.data.images.js');
for (const image of demoDataImages.demoImages) {
const imageInstance = new cloudlyRef.imageManager.CImage();
Object.assign(imageInstance, image);
await imageInstance.save();
}
}

View File

@@ -1,12 +1,12 @@
import * as plugins from './cloudly.plugins.js';
import { CloudlyConfig } from './cloudly.classes.config.js';
import * as plugins from './plugins.js';
import { CloudlyConfig } from './classes.config.js';
// interfaces
import {} from '@tsclass/tsclass';
// Cloudly mods
import { CloudlyInfo } from './cloudly.classes.cloudlyinfo.js';
import { CloudlyServer } from './cloudly.classes.server.js';
import { CloudlyInfo } from './classes.cloudlyinfo.js';
import { CloudlyServer } from './classes.server.js';
// connectors
import { CloudflareConnector } from './connector.cloudflare/connector.js';
@@ -15,14 +15,15 @@ import { MongodbConnector } from './connector.mongodb/connector.js';
// processes
import { CloudlyCoreflowManager } from './manager.coreflow/coreflowmanager.js';
import { ClusterManager } from './manager.cluster/clustermanager.js';
import { ClusterManager } from './manager.cluster/classes.clustermanager.js';
import { CloudlyTaskmanager } from './manager.task/taskmanager.js';
import { CloudlyVersionManager } from './manager.version/versionmanager.js';
import { CloudlySecretManager } from './manager.secret/classes.secretmanager.js'
import { CloudlyServerManager } from './manager.server/servermanager.js';
import { CloudlySecretManager } from './manager.secret/classes.secretmanager.js';
import { CloudlyServerManager } from './manager.server/classes.servermanager.js';
import { ExternalApiManager } from './manager.status/statusmanager.js';
import { ExternalRegistryManager } from './manager.externalregistry/index.js';
import { ImageManager } from './manager.image/classes.imagemanager.js';
import { logger } from './cloudly.logging.js';
import { logger } from './logger.js';
import { CloudlyAuthManager } from './manager.auth/classes.authmanager.js';
/**
* Cloudly class can be used to instantiate a cloudly server.
@@ -49,18 +50,21 @@ export class Cloudly {
public mongodbConnector: MongodbConnector;
// managers
public authManager: CloudlyAuthManager;
public secretManager: CloudlySecretManager;
public clusterManager: ClusterManager;
public coreflowManager: CloudlyCoreflowManager;
public externalApiManager: ExternalApiManager;
public externalRegistryManager: ExternalRegistryManager;
public imageManager: ImageManager;
public taskManager: CloudlyTaskmanager;
public versionManager: CloudlyVersionManager;
public serverManager: CloudlyServerManager;
private readyDeferred = new plugins.smartpromise.Deferred();
constructor() {
private configOptions: plugins.servezoneInterfaces.data.ICloudlyConfig;
constructor(configArg?: plugins.servezoneInterfaces.data.ICloudlyConfig) {
this.configOptions = configArg;
this.cloudlyInfo = new CloudlyInfo(this);
this.config = new CloudlyConfig(this);
@@ -73,13 +77,14 @@ export class Cloudly {
this.cloudflareConnector = new CloudflareConnector(this);
this.letsencryptConnector = new LetsencryptConnector(this);
// processes
// managers
this.authManager = new CloudlyAuthManager(this);
this.clusterManager = new ClusterManager(this);
this.coreflowManager = new CloudlyCoreflowManager(this);
this.externalApiManager = new ExternalApiManager(this);
this.externalRegistryManager = new ExternalRegistryManager(this);
this.imageManager = new ImageManager(this);
this.taskManager = new CloudlyTaskmanager(this);
this.versionManager = new CloudlyVersionManager(this);
this.secretManager = new CloudlySecretManager(this);
this.serverManager = new CloudlyServerManager(this);
}
@@ -90,9 +95,10 @@ export class Cloudly {
*/
public async start() {
// config
await this.config.init();
await this.config.init(this.configOptions);
// manageers
await this.authManager.start();
await this.secretManager.start();
await this.serverManager.start();

View File

@@ -1,5 +1,5 @@
import * as plugins from './cloudly.plugins.js';
import * as paths from './cloudly.paths.js';
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import { Cloudly } from './index.js';
export class CloudlyInfo {

71
ts/classes.config.ts Normal file
View File

@@ -0,0 +1,71 @@
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import { logger } from './logger.js';
import type { Cloudly } from './classes.cloudly.js';
/**
* the main cloudly config
*/
export class CloudlyConfig {
public cloudlyRef: Cloudly;
public appData: plugins.npmextra.AppData<plugins.servezoneInterfaces.data.ICloudlyConfig>;
public data: plugins.servezoneInterfaces.data.ICloudlyConfig;
constructor(cloudlyRefArg: Cloudly) {
this.cloudlyRef = cloudlyRefArg;
}
public async init(configArg?: plugins.servezoneInterfaces.data.ICloudlyConfig) {
this.appData =
await plugins.npmextra.AppData.createAndInit<plugins.servezoneInterfaces.data.ICloudlyConfig>(
{
envMapping: {
cfToken: 'CF_TOKEN',
environment: 'SERVEZONE_ENVIRONMENT' as 'production' | 'integration',
letsEncryptEmail: 'hard:domains@lossless.org',
hetznerToken: 'HETZNER_API_TOKEN',
letsEncryptPrivateKey: null,
publicUrl: 'SERVEZONE_URL',
publicPort: 'SERVEZONE_PORT',
mongoDescriptor: {
mongoDbUrl: 'MONGODB_URL',
mongoDbName: 'MONGODB_NAME',
mongoDbUser: 'MONGODB_USER',
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: 'boolean:S3_USESSL' as any as boolean,
bucketName: 'S3_BUCKET'
},
sslMode:
'SERVEZONE_SSLMODE' as plugins.servezoneInterfaces.data.ICloudlyConfig['sslMode'],
servezoneAdminaccount: 'SERVEZONE_ADMINACCOUNT',
},
requiredKeys: [
'cfToken',
'hetznerToken',
'letsEncryptEmail',
'publicUrl',
'publicPort',
'sslMode',
'environment',
'mongoDescriptor',
],
overwriteObject: configArg,
},
);
const kvStore = await this.appData.getKvStore();
this.data = await kvStore.readAll();
const missingKeys = await this.appData.logMissingKeys();
if (missingKeys.length > 0) {
logger.log('error', `missing keys: ${missingKeys.join(', ')}`);
throw new Error('missing keys');
}
}
}

View File

@@ -1,7 +1,7 @@
import * as plugins from './cloudly.plugins.js';
import * as paths from './cloudly.paths.js';
import { Cloudly } from './cloudly.classes.cloudly.js';
import { logger } from './cloudly.logging.js';
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import { Cloudly } from './classes.cloudly.js';
import { logger } from './logger.js';
/**
* handles incoming requests from CI to deploy new versions of apps
@@ -10,7 +10,8 @@ export class CloudlyServer {
/**
* a reference to the cloudly instance
*/
private cloudlyRef: Cloudly;
public cloudlyRef: Cloudly;
public additionalHandlers: plugins.typedserver.servertools.Handler[] = [];
/**
* the smartexpress server handling the actual requests
@@ -37,18 +38,24 @@ export class CloudlyServer {
* init the reception instance
*/
public async start() {
logger.log('info', `cloudly domain is ${this.cloudlyRef.config.data.publicUrl}`)
logger.log('info', `cloudly domain is ${this.cloudlyRef.config.data.publicUrl}`);
let sslCert: plugins.smartacme.Cert;
if (this.cloudlyRef.config.data.sslMode === 'letsencrypt') {
logger.log('info', `Using letsencrypt for ssl mode. Trying to obtain a certificate...`)
logger.log('info', `This might take 10 minutes...`)
logger.log('info', `Using letsencrypt for ssl mode. Trying to obtain a certificate...`);
logger.log('info', `This might take 10 minutes...`);
sslCert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain(
this.cloudlyRef.config.data.publicUrl
this.cloudlyRef.config.data.publicUrl,
);
logger.log(
'success',
`Successfully obtained certificate for cloudly domain ${this.cloudlyRef.config.data.publicUrl}`,
);
logger.log('success', `Successfully obtained certificate for cloudly domain ${this.cloudlyRef.config.data.publicUrl}`)
} else if (this.cloudlyRef.config.data.sslMode === 'external') {
logger.log('info', `Using external certificate for ssl mode, meaning cloudly is not in charge of ssl termination.`)
logger.log(
'info',
`Using external certificate for ssl mode, meaning cloudly is not in charge of ssl termination.`,
);
}
interface IRequestGuardData {
@@ -72,11 +79,13 @@ export class CloudlyServer {
this.typedServer = new plugins.typedserver.TypedServer({
cors: true,
forceSsl: false,
port: this.cloudlyRef.config.data.publicPort,
...(sslCert ? {
privateKey: sslCert.privateKey,
publicKey: sslCert.publicKey,
} : {}),
port: this.cloudlyRef.config.data.publicPort,
...(sslCert
? {
privateKey: sslCert.privateKey,
publicKey: sslCert.publicKey,
}
: {}),
injectReload: true,
serveDir: paths.distServeDir,
watch: true,
@@ -84,6 +93,10 @@ export class CloudlyServer {
preferredCompressionMethod: 'gzip',
});
this.typedServer.typedrouter.addTypedRouter(this.typedrouter);
this.typedServer.server.addRoute(
'/curlfresh/:scriptname',
this.cloudlyRef.serverManager.curlfreshInstance.handler,
);
await this.typedServer.start();
}

View File

@@ -1,79 +0,0 @@
import * as plugins from './cloudly.plugins.js';
import * as paths from './cloudly.paths.js';
import { logger } from './cloudly.logging.js';
import type { Cloudly } from './cloudly.classes.cloudly.js';
/**
* the main cloudly config
*/
export class CloudlyConfig {
public cloudlyRef: Cloudly;
public appData: plugins.npmextra.AppData<plugins.servezoneInterfaces.data.ICloudlyConfig>;
public data: plugins.servezoneInterfaces.data.ICloudlyConfig
// authentication and settings
public smartjwtInstance: plugins.smartjwt.SmartJwt;
constructor(cloudlyRefArg: Cloudly) {
this.cloudlyRef = cloudlyRefArg;
}
public async init() {
this.appData = await plugins.npmextra.AppData.createAndInit<plugins.servezoneInterfaces.data.ICloudlyConfig>({
envMapping: {
cfToken: 'CF_TOKEN',
environment: 'SERVEZONE_ENVIRONMENT' as 'production' | 'integration',
letsEncryptEmail: 'hard:domains@lossless.org',
hetznerToken: 'HETZNER_API_TOKEN',
letsEncryptPrivateKey: null,
publicUrl: 'SERVEZONE_URL',
publicPort: 'SERVEZONE_PORT',
mongoDescriptor: {
mongoDbUrl: 'MONGODB_URL',
mongoDbName: 'MONGODB_DATABASE',
mongoDbUser: 'MONGODB_USER',
mongoDbPass: 'MONGODB_PASSWORD',
},
s3Descriptor: {
endpoint: 'S3_ENDPOINT',
accessKey: 'S3_ACCESSKEY',
accessSecret: 'S3_SECRETKEY',
port: 'S3_PORT', // Note: This will remain as a string. Ensure to parse it to an integer where it's used.
useSsl: true,
},
sslMode: 'SERVEZONE_SSLMODE' as plugins.servezoneInterfaces.data.ICloudlyConfig['sslMode'],
},
requiredKeys: [
'cfToken',
'hetznerToken',
'letsEncryptEmail',
'publicUrl',
'publicPort',
'sslMode',
'environment',
'mongoDescriptor',
],
});
this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
const kvStore = await this.appData.getKvStore();
const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = await kvStore.readKey('jwtKeys');
if (!existingJwtKeys) {
await this.smartjwtInstance.createNewKeyPair();
const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson();
await kvStore.writeKey('jwtKeys', newJwtKeys);
} else {
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
}
this.data = await kvStore.readAll();
const missingKeys = await this.appData.logMissingKeys();
if (missingKeys.length > 0) {
logger.log('error', `missing keys: ${missingKeys.join(', ')}`);
throw new Error('missing keys');
}
}
}

View File

@@ -1,5 +0,0 @@
import * as plugins from './cloudly.plugins.js';
export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '../');
export const nogitDir = plugins.path.join(packageDir, '.nogit/');
export const distServeDir = plugins.path.join(packageDir, './dist_serve');

View File

@@ -1,5 +1,5 @@
import * as plugins from '../cloudly.plugins.js';
import { Cloudly } from '../cloudly.classes.cloudly.js';
import * as plugins from '../plugins.js';
import { Cloudly } from '../classes.cloudly.js';
/**
* the portion of Cloudflare responsible

View File

@@ -1,5 +1,5 @@
import * as plugins from '../cloudly.plugins.js';
import { Cloudly } from '../cloudly.classes.cloudly.js';
import * as plugins from '../plugins.js';
import { Cloudly } from '../classes.cloudly.js';
export class LetsencryptConnector {
private cloudlyRef: Cloudly;
@@ -18,23 +18,27 @@ 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);
console.log(`trying again in a few minutes`);
});
await this.smartacme.init();
}
/**

View File

@@ -1,5 +1,5 @@
import * as plugins from '../cloudly.plugins.js';
import { Cloudly } from '../cloudly.classes.cloudly.js';
import * as plugins from '../plugins.js';
import { Cloudly } from '../classes.cloudly.js';
export class MongodbConnector {
// INSTANCE
@@ -11,7 +11,9 @@ export class MongodbConnector {
}
public async init() {
this.smartdataDb = new plugins.smartdata.SmartdataDb(this.cloudlyRef.config.data.mongoDescriptor);
this.smartdataDb = new plugins.smartdata.SmartdataDb(
this.cloudlyRef.config.data.mongoDescriptor,
);
await this.smartdataDb.init();
}

View File

@@ -1,13 +1,12 @@
import * as early from '@push.rocks/early';
early.start('cloudly');
import * as plugins from './cloudly.plugins.js';
import * as paths from './cloudly.paths.js';
import { Cloudly } from './cloudly.classes.cloudly.js';
import { logger } from './cloudly.logging.js';
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import { Cloudly } from './classes.cloudly.js';
import { logger } from './logger.js';
const cloudlyQenv = new plugins.qenv.Qenv(paths.packageDir, paths.nogitDir, true);
early.stop();
/**
* starts the cloudly instance
*/
@@ -17,12 +16,14 @@ const runCli = async () => {
logger.log(
'info',
`running in environment ${await cloudlyQenv.getEnvVarOnDemand('SERVEZONE_ENVIRONMENT')}`
`running in environment ${await cloudlyQenv.getEnvVarOnDemand('SERVEZONE_ENVIRONMENT')}`,
);
await cloudlyInstance.start();
const demoMod = await import('./demo/index.js');
const demoMod = await import('./00demo/index.js');
demoMod.installDemoData(cloudlyInstance);
};
export { runCli, Cloudly };
type ICloudlyConfig = plugins.servezoneInterfaces.data.ICloudlyConfig;
export { type ICloudlyConfig };

View File

@@ -1,5 +1,5 @@
import * as plugins from './cloudly.plugins.js';
import * as paths from './cloudly.paths.js';
import * as plugins from './plugins.js';
import * as paths from './paths.js';
export const logger = new plugins.smartlog.Smartlog({
logContext: {
@@ -9,8 +9,8 @@ export const logger = new plugins.smartlog.Smartlog({
zone: null,
companyunit: null,
containerName: null,
}
},
});
logger.enableConsole({
captureAll: false
captureAll: false,
});

View File

@@ -0,0 +1,137 @@
import * as plugins from '../plugins.js';
import type { Cloudly } from '../classes.cloudly.js';
import { logger } from '../logger.js';
import { Authorization } from './classes.authorization.js';
import { User } from './classes.user.js';
export interface IJwtData {
userId: string;
status: 'loggedIn' | 'loggedOut';
expiresAt: number;
}
export class CloudlyAuthManager {
cloudlyRef: Cloudly;
public get db() {
return this.cloudlyRef.mongodbConnector.smartdataDb;
}
public CUser = plugins.smartdata.setDefaultManagerForDoc(this, User);
public CAuthorization = plugins.smartdata.setDefaultManagerForDoc(this, Authorization);
public typedrouter = new plugins.typedrequest.TypedRouter();
public smartjwtInstance: plugins.smartjwt.SmartJwt<IJwtData>;
constructor(cloudlyRef: Cloudly) {
this.cloudlyRef = cloudlyRef;
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
}
public async createNewSecureToken() {
return plugins.smartunique.uniSimple('secureToken', 64);
}
public async start() {
// lets setup the smartjwtInstance
this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
await this.smartjwtInstance.init();
const kvStore = await this.cloudlyRef.config.appData.getKvStore();
const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = (await kvStore.readKey(
'jwtKeypair',
)) as plugins.tsclass.network.IJwtKeypair;
if (!existingJwtKeys) {
await this.smartjwtInstance.createNewKeyPair();
const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson();
await kvStore.writeKey('jwtKeypair', newJwtKeys);
} else {
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
}
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>(
'adminLoginWithUsernameAndPassword',
async (dataArg) => {
let jwt: string;
let expiresAtTimestamp: number = Date.now() + 3600 * 1000 * 24 * 7;
const user = await User.findUserByUsernameAndPassword(dataArg.username, dataArg.password);
if (!user) {
logger.log('warn', 'login failed');
throw new plugins.typedrequest.TypedResponseError('login failed');
} else {
jwt = await this.smartjwtInstance.createJWT({
userId: user.id,
status: 'loggedIn',
expiresAt: expiresAtTimestamp,
});
logger.log('success', 'login successful');
}
return {
identity: {
jwt,
userId: user.id,
name: user.data.username,
expiresAt: expiresAtTimestamp,
role: user.data.role,
type: user.data.type,
},
};
},
),
);
}
public async stop() {}
public validIdentityGuard = new plugins.smartguard.Guard<{
identity: plugins.servezoneInterfaces.data.IIdentity;
}>(
async (dataArg) => {
const jwt = dataArg.identity.jwt;
const jwtData: IJwtData = await this.smartjwtInstance.verifyJWTAndGetData(jwt);
const expired = jwtData.expiresAt < Date.now();
plugins.smartexpect
.expect(jwtData.status)
.setFailMessage('user not logged in')
.toEqual('loggedIn');
plugins.smartexpect.expect(expired).setFailMessage(`jwt expired`).toBeFalse();
plugins.smartexpect
.expect(dataArg.identity.expiresAt)
.setFailMessage(
`expiresAt >>identity valid until:${dataArg.identity.expiresAt}, but jwt says: ${jwtData.expiresAt}<< has been tampered with`,
)
.toEqual(jwtData.expiresAt);
plugins.smartexpect
.expect(dataArg.identity.userId)
.setFailMessage('userId has been tampered with')
.toEqual(jwtData.userId);
if (expired) {
throw new Error('identity is expired');
}
return true;
},
{
failedHint: 'identity is not valid.',
name: 'validIdentityGuard',
},
);
public adminIdentityGuard = new plugins.smartguard.Guard<{
identity: plugins.servezoneInterfaces.data.IIdentity;
}>(
async (dataArg) => {
await plugins.smartguard.passGuardsOrReject(dataArg, [this.validIdentityGuard]);
const jwt = dataArg.identity.jwt;
const jwtData: IJwtData = await this.smartjwtInstance.verifyJWTAndGetData(jwt);
const user = await this.CUser.getInstance({ id: jwtData.userId });
const isAdminBool = user.data.role === 'admin';
console.log(`user is admin: ${isAdminBool}`);
return isAdminBool;
},
{
failedHint: 'user is not admin.',
name: 'adminIdentityGuard',
},
);
}

View File

@@ -0,0 +1,4 @@
import * as plugins from '../plugins.js';
@plugins.smartdata.managed()
export class Authorization extends plugins.smartdata.SmartDataDbDoc<Authorization, Authorization> {}

View File

@@ -0,0 +1,52 @@
import * as plugins from '../plugins.js';
@plugins.smartdata.managed()
export class User extends plugins.smartdata.SmartDataDbDoc<
User,
plugins.servezoneInterfaces.data.IUser
> {
/**
* creates a machine user
*/
public static async createMachineUser(userNameArg: string, roleArg: 'api' | 'cluster') {
const user = new User();
user.id = await User.getNewId();
user.data = {
type: 'machine',
username: userNameArg,
tokens: [
{
token: 'machineUser',
expiresAt: Date.now() + 3600 * 1000 * 24 * 365,
assignedRoles: ['admin'],
},
],
role: 'api',
};
await user.save();
return user;
}
public static async findUserByUsernameAndPassword(usernameArg: string, passwordArg: string) {
return await User.getInstance({
data: {
username: usernameArg,
password: passwordArg,
},
});
}
constructor(optionsArg?: plugins.servezoneInterfaces.data.IUser) {
super();
if (optionsArg) {
Object.assign(this, optionsArg);
}
}
// INSTANCE
@plugins.smartdata.unI()
public id: string;
@plugins.smartdata.svDb()
public data: plugins.servezoneInterfaces.data.IUser['data'];
}

3
ts/manager.cert/cert.ts Normal file
View File

@@ -0,0 +1,3 @@
import * as plugins from '../plugins.js';
export class Cert extends plugins.smartdata.SmartDataDbDoc<Cert, Cert> {}

View File

@@ -0,0 +1,14 @@
import type { Cloudly } from '../classes.cloudly.js';
import * as plugins from '../plugins.js';
export class CertManager {
public cloudlyRef: Cloudly;
public get db() {
return this.cloudlyRef.mongodbConnector.smartdataDb;
}
constructor(cloudly: Cloudly) {
this.cloudlyRef = cloudly;
}
}

View File

@@ -1,14 +1,15 @@
import * as plugins from '../cloudly.plugins.js';
import * as plugins from '../plugins.js';
/*
* cluster defines a swarmkit cluster
*/
@plugins.smartdata.Manager()
export class Cluster extends plugins.smartdata.SmartDataDbDoc<Cluster, plugins.servezoneInterfaces.data.ICluster> {
@plugins.smartdata.managed()
export class Cluster extends plugins.smartdata.SmartDataDbDoc<
Cluster,
plugins.servezoneInterfaces.data.ICluster
> {
// STATIC
public static async fromConfigObject(
configObjectArg: plugins.servezoneInterfaces.data.ICluster
) {
public static async fromConfigObject(configObjectArg: plugins.servezoneInterfaces.data.ICluster) {
const newCluster = new Cluster();
Object.assign(newCluster, configObjectArg);
return newCluster;

View File

@@ -1,9 +1,10 @@
import * as plugins from '../cloudly.plugins.js';
import * as paths from '../cloudly.paths.js';
import { Cloudly } from '../cloudly.classes.cloudly.js';
import { logger } from '../cloudly.logging.js';
import * as plugins from '../plugins.js';
import * as paths from '../paths.js';
import { Cloudly } from '../classes.cloudly.js';
import { logger } from '../logger.js';
import { Cluster } from './cluster.js';
import { Cluster } from './classes.cluster.js';
import { data } from '@serve.zone/interfaces';
export class ClusterManager {
public ready = plugins.smartpromise.defer();
@@ -22,13 +23,12 @@ export class ClusterManager {
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_CreateCluster>(
new plugins.typedrequest.TypedHandler('createCluster', async (dataArg) => {
const cluster = await this.storeCluster({
// TODO: guards
const cluster = await this.createCluster({
id: plugins.smartunique.uniSimple('cluster'),
data: {
userId: null, // this is created by the createCluster method
name: dataArg.clusterName,
jumpCode: plugins.smartunique.uniSimple('cluster'),
jumpCodeUsedAt: null,
secretKey: plugins.smartunique.shortId(16),
acmeInfo: null,
cloudlyUrl: `https://${this.cloudlyRef.config.data.publicUrl}:${this.cloudlyRef.config.data.publicPort}/`,
servers: [],
@@ -38,21 +38,32 @@ export class ClusterManager {
console.log(await cluster.createSavableObject());
this.cloudlyRef.serverManager.ensureServerInfrastructure();
return {
clusterConfig: await cluster.createSavableObject(),
cluster: await cluster.createSavableObject(),
};
})
}),
);
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_GetAllClusters>(
new plugins.typedrequest.TypedHandler('getAllClusters', async (dataArg) => {
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IReq_Any_Cloudly_GetClusters>(
new plugins.typedrequest.TypedHandler('getClusters', async (dataArg) => {
// TODO: do authentication here
const clusters = await this.getAllClusters();
return {
clusters: await Promise.all(
clusters.map((clusterArg) => clusterArg.createSavableObject())
clusters.map((clusterArg) => clusterArg.createSavableObject()),
),
};
})
}),
);
// delete cluster
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IReq_Any_Cloudly_DeleteClusterById>(
new plugins.typedrequest.TypedHandler('deleteClusterById', async (reqDataArg, toolsArg) => {
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqDataArg);
await this.deleteCluster(reqDataArg.clusterId);
return {
ok: true,
};
}),
);
}
@@ -70,25 +81,22 @@ export class ClusterManager {
// TODO: implement getclusterConfigByServerIp
}
public async getClusterConfigBy_JumpCode(jumpCodeArg: string) {
public async getClusterBy_UserId(userIdArg: string) {
await this.ready.promise;
return await Cluster.getInstance({
data: {
jumpCode: jumpCodeArg,
userId: userIdArg,
},
});
}
public async getClusterConfigBy_ClusterIdentifier(
clusterIdentifier: plugins.servezoneInterfaces.data.IClusterIdentifier
) {
public async getClusterBy_Identity(clusterIdentity: plugins.servezoneInterfaces.data.IIdentity) {
await this.ready.promise;
return await Cluster.getInstance({
data: {
name: clusterIdentifier.clusterName,
secretKey: clusterIdentifier.secretKey,
userId: clusterIdentity.userId,
},
});
}
@@ -114,18 +122,39 @@ export class ClusterManager {
}
/**
* allows storage of a config
* creates a cluster (and a new user for it) and saves it
* @param configName
* @param configObjectArg
*/
public async storeCluster(configObjectArg: plugins.servezoneInterfaces.data.ICluster) {
let clusterInstance = await Cluster.getInstance({ id: configObjectArg.id });
if (!clusterInstance) {
clusterInstance = await Cluster.fromConfigObject(configObjectArg);
} else {
Object.assign(clusterInstance, configObjectArg);
}
public async createCluster(configObjectArg: plugins.servezoneInterfaces.data.ICluster) {
// TODO: guards
// lets create the cluster user
const clusterUser = new this.cloudlyRef.authManager.CUser({
id: await this.cloudlyRef.authManager.CUser.getNewId(),
data: {
role: 'cluster',
type: 'machine',
tokens: [
{
expiresAt: Date.now() + 3600 * 1000 * 24 * 365,
assignedRoles: ['cluster'],
token: await this.cloudlyRef.authManager.createNewSecureToken(),
},
],
},
});
await clusterUser.save();
Object.assign(configObjectArg, {
userId: clusterUser.id,
});
const clusterInstance = await Cluster.fromConfigObject(configObjectArg);
await clusterInstance.save();
return clusterInstance;
}
public async deleteCluster(clusterId: string) {
await this.ready.promise;
const clusterInstance = await Cluster.getInstance({ id: clusterId });
await clusterInstance.delete();
}
}

View File

@@ -1,5 +1,6 @@
import * as plugins from '../cloudly.plugins.js';
import { Cloudly } from '../cloudly.classes.cloudly.js';
import * as plugins from '../plugins.js';
import { Cloudly } from '../classes.cloudly.js';
import type { Cluster } from '../manager.cluster/classes.cluster.js';
/**
* in charge of talking to coreflow services on clusters
@@ -13,21 +14,50 @@ export class CloudlyCoreflowManager {
this.cloudlyRef = cloudlyRefArg;
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByJumpCode>(
new plugins.typedrequest.TypedHandler('getIdentityByJumpCode', async (requestData) => {
const clusterConfig =
await this.cloudlyRef.clusterManager.getClusterConfigBy_JumpCode(
requestData.jumpCode
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken>(
new plugins.typedrequest.TypedHandler('getIdentityByToken', async (requestData) => {
// Use getInstance with $elemMatch for querying nested arrays
const user = await this.cloudlyRef.authManager.CUser.getInstance({
data: {
tokens: {
$elemMatch: { token: requestData.token },
},
},
});
if (!user) {
throw new plugins.typedrequest.TypedResponseError(
'The supplied token is not valid. No matching user found.'
);
if (!clusterConfig) {
throw new plugins.typedrequest.TypedResponseError('The supplied jumpCode is not valid.');
}
if (user.data.type !== 'machine') {
throw new plugins.typedrequest.TypedResponseError(
'The supplied token is not valid. The user is not a machine.'
);
}
let cluster: Cluster;
if (user.data.role === 'cluster') {
cluster = await this.cloudlyRef.clusterManager.getClusterBy_UserId(user.id);
}
const expiryTimestamp = Date.now() + 3600 * 1000 * 24 * 365;
return {
clusterIdentifier: {
clusterName: clusterConfig.data.name,
secretKey: clusterConfig.data.secretKey,
identity: {
name: user.data.username,
role: user.data.role,
type: 'machine', // if someone authenticates by token, they are a machine, no matter what.
userId: user.id,
expiresAt: expiryTimestamp,
...(cluster
? {
clusterId: cluster.id,
clusterName: cluster.data.name,
}
: {}),
jwt: await this.cloudlyRef.authManager.smartjwtInstance.createJWT({
status: 'loggedIn',
userId: user.id,
expiresAt: expiryTimestamp,
}),
},
};
})
@@ -38,16 +68,14 @@ export class CloudlyCoreflowManager {
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>(
'getClusterConfig',
async (dataArg) => {
const clusterIdentifier = dataArg.clusterIdentifier;
const identity = dataArg.identity;
console.log('trying to get clusterConfigSet');
console.log(dataArg);
const clusterConfigSet =
await this.cloudlyRef.clusterManager.getClusterConfigBy_ClusterIdentifier(
clusterIdentifier
);
const cluster = await this.cloudlyRef.clusterManager.getClusterBy_Identity(identity);
console.log('got cluster config and sending it back to coreflow');
return {
configData: await clusterConfigSet.createSavableObject()
configData: await cluster.createSavableObject(),
services: [],
};
}
)
@@ -55,16 +83,16 @@ export class CloudlyCoreflowManager {
// lets enable getting of certificates
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetSslCertificate>(
'getSslCertificate',
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain>(
'getCertificateForDomain',
async (dataArg) => {
console.log(`got request for certificate ${dataArg.requiredCertName}`);
console.log(`incoming API request for certificate ${dataArg.domainName}`);
const cert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain(
dataArg.requiredCertName
dataArg.domainName
);
console.log(`got certificate ready for reponse ${dataArg.requiredCertName}`);
console.log(`got certificate ready for reponse ${dataArg.domainName}`);
return {
certificate: await cert.createSavableObject(),
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

@@ -1,12 +1,26 @@
import * as plugins from '../cloudly.plugins.js';
import * as plugins from '../plugins.js';
import type { ImageManager } from './classes.imagemanager.js';
@plugins.smartdata.Manager()
export class Image extends plugins.smartdata.SmartDataDbDoc<Image, plugins.servezoneInterfaces.data.IImage, ImageManager> {
public static async create(imageDataArg: Partial<plugins.servezoneInterfaces.data.IImage['data']>) {
@plugins.smartdata.managed()
export class Image extends plugins.smartdata.SmartDataDbDoc<
Image,
plugins.servezoneInterfaces.data.IImage,
ImageManager
> {
public static async create(
imageDataArg: Partial<plugins.servezoneInterfaces.data.IImage['data']>,
) {
const image = new Image();
image.id = plugins.smartunique.uni('image');
Object.assign(image.data, imageDataArg);
image.id = await this.getNewId();
console.log(imageDataArg);
Object.assign(image, {
data: {
name: imageDataArg.name,
description: imageDataArg.description,
versions: [],
},
});
console.log((Image as any).saveableProperties);
await image.save();
return image;
}
@@ -18,4 +32,16 @@ export class Image extends plugins.smartdata.SmartDataDbDoc<Image, plugins.serve
public data: plugins.servezoneInterfaces.data.IImage['data'];
public async getVersions() {}
}
/**
* returns a storage path
* note: this is relative to the storage method defined by the imageManager
*/
public async getStoragePath(versionStringArg: string) {
return `${this.data.name}:${versionStringArg}`.replace('/', '__');
}
public async getWriteStream() {}
public async getReadStream() {}
}

View File

@@ -1,93 +1,173 @@
import type { Cloudly } from '../cloudly.classes.cloudly.js';
import * as plugins from '../cloudly.plugins.js';
import type { Cloudly } from '../classes.cloudly.js';
import * as plugins from '../plugins.js';
import * as paths from '../paths.js';
import { Image } from './classes.image.js';
export class ImageManager {
cloudlyRef: Cloudly;
public typedrouter = new plugins.typedrequest.TypedRouter();
public smartbucketInstance: plugins.smartbucket.SmartBucket;
public imageDir: plugins.smartbucket.Directory;
public dockerImageStore: plugins.docker.DockerImageStore;
get db() {
return this.cloudlyRef.mongodbConnector.smartdataDb;
}
public typedrouter = new plugins.typedrequest.TypedRouter();
public CImage = plugins.smartdata.setDefaultManagerForDoc(this, Image);
smartbucketInstance: plugins.smartbucket.SmartBucket;
constructor(cloudlyRefArg: Cloudly) {
this.cloudlyRef = cloudlyRefArg;
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
'createImage',
async (reqArg, toolsArg) => {
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
const image = await this.CImage.create({
name: reqArg.name,
description: reqArg.description,
versions: [],
});
return {
image: await image.createSavableObject(),
};
},
),
);
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetImage>(
new plugins.typedrequest.TypedHandler('getImage', async (reqArg, toolsArg) => {
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
const image = await this.CImage.getInstance({
id: reqArg.imageId,
});
return {
image: await image.createSavableObject(),
};
}),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_DeleteImage>(
'deleteImage',
async (reqArg, toolsArg) => {
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
const image = await this.CImage.getInstance({
id: reqArg.imageId,
});
await image.delete();
return {};
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
'getAllImages',
async (requestArg) => {
async (requestArg, toolsArg) => {
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], requestArg);
const images = await this.CImage.getInstances({});
return {
images: await Promise.all(
images.map((image) => {
return image.createSavableObject();
})
}),
),
};
}
)
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
'createImage',
async (reqArg) => {
const image = await this.CImage.create({
name: reqArg.name,
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PushImageVersion>(
'pushImageVersion',
async (reqArg, toolsArg) => {
await plugins.smartguard.passGuardsOrReject(reqArg, [
this.cloudlyRef.authManager.validIdentityGuard,
]);
const refImage = await this.CImage.getInstance({
id: reqArg.imageId,
});
if (!refImage) {
throw new plugins.typedrequest.TypedResponseError('Image not found');
}
const imageVersion = reqArg.versionString;
console.log(
`got request to push image version ${imageVersion} for image ${refImage.data.name}`,
);
const imagePushStream = reqArg.imageStream;
(async () => {
const smartWebDuplex = new plugins.smartstream.webstream.WebDuplexStream<
Uint8Array,
Uint8Array
>({
writeFunction: async (chunkArg, toolsArg) => {
console.log(chunkArg);
return chunkArg;
},
});
imagePushStream.writeToWebstream(smartWebDuplex.writable);
await this.dockerImageStore.storeImage(
refImage.id,
plugins.smartstream.SmartDuplex.fromWebReadableStream(smartWebDuplex.readable),
);
})();
return {
image: await image.createSavableObject(),
allowed: true,
};
}
)
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PushImage>('pushImage', async (reqArg) => {
const pushStream = reqArg.imageStream;
return {}
})
)
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PullImage>(
'pullImage',
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PullImageVersion>(
'pullImageVersion',
async (reqArg) => {
const image = await this.CImage.getInstance({
data: {
name: reqArg.name,
}
id: reqArg.imageId,
});
const imageVersion = null;
const imageVersion = image.data.versions.find(
(version) => version.versionString === reqArg.versionString,
);
const readable = this.imageDir.fastGetStream(
{
path: await image.getStoragePath(reqArg.versionString),
},
'webstream',
);
const imageVirtualStream = new plugins.typedrequest.VirtualStream();
return {
imageStream: imageVirtualStream,
}
}
)
};
},
),
);
}
public async start() {
// lets setup s3
const s3Descriptor: plugins.tsclass.storage.IS3Descriptor =
await this.cloudlyRef.config.appData.waitForAndGetKey('s3Descriptor');
console.log(this.cloudlyRef.config.data.s3Descriptor);
this.smartbucketInstance = new plugins.smartbucket.SmartBucket(
this.cloudlyRef.config.data.s3Descriptor
this.cloudlyRef.config.data.s3Descriptor,
);
const bucket = await this.smartbucketInstance.getBucketByName('cloudly-test');
await bucket.fastStore('test/test.txt', 'hello');
}
const bucket = await this.smartbucketInstance.getBucketByName(s3Descriptor.bucketName);
await bucket.fastPut({ path: 'images/00init', contents: 'init' });
public async createImage(nameArg: string) {
const newImage = await this.CImage.create({
name: nameArg,
this.imageDir = await bucket.getDirectoryFromPath({
path: '/images',
});
// lets setup dockerstore
await plugins.smartfile.fs.ensureDir(paths.dockerImageStoreDir);
this.dockerImageStore = new plugins.docker.DockerImageStore({
localDirPath: paths.dockerImageStoreDir,
bucketDir: this.imageDir,
});
}
}

View File

@@ -1,5 +1,5 @@
import * as plugins from '../cloudly.plugins.js';
import { Cloudly } from '../cloudly.classes.cloudly.js';
import * as plugins from '../plugins.js';
import { Cloudly } from '../classes.cloudly.js';
/**
* takes care of receiving and providing logs
@@ -12,4 +12,4 @@ export class LogManager {
this.cloudlyRef = cloudlyRefArg;
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
}
}
}

View File

@@ -1,7 +1,7 @@
// a secret bundle is a set of secrets ready to be used in a project.
// it bundles secretgroups
import { SecretGroup } from './classes.secretgroup.js';
import * as plugins from '../cloudly.plugins.js';
import * as plugins from '../plugins.js';
@plugins.smartdata.Manager()
export class SecretBundle extends plugins.smartdata.SmartDataDbDoc<
@@ -23,7 +23,7 @@ export class SecretBundle extends plugins.smartdata.SmartDataDbDoc<
secretGroups.push(
await SecretGroup.getInstance({
id: secretGroupId,
})
}),
);
}
return secretGroups;
@@ -59,4 +59,16 @@ export class SecretBundle extends plugins.smartdata.SmartDataDbDoc<
}
return returnObject;
}
public async getFlatKeyValueObject(environmentArg: string) {
if (!environmentArg) {
throw new Error('environment is required');
}
const secretGroups = await this.getSecretGroups();
const returnObject = {};
for (const secretGroup of secretGroups) {
returnObject[secretGroup.data.key] = secretGroup.data.environments[environmentArg].value;
}
return returnObject;
}
}

View File

@@ -1,7 +1,7 @@
/**
* a secretgroup is a set of secrets for different environments.
*/
import * as plugins from '../cloudly.plugins.js';
import * as plugins from '../plugins.js';
@plugins.smartdata.Manager()
export class SecretGroup extends plugins.smartdata.SmartDataDbDoc<

View File

@@ -1,9 +1,9 @@
import * as plugins from '../cloudly.plugins.js';
import * as paths from '../cloudly.paths.js';
import * as plugins from '../plugins.js';
import * as paths from '../paths.js';
import { SecretBundle } from './classes.secretbundle.js';
import { SecretGroup } from './classes.secretgroup.js';
import { logger } from '../cloudly.logging.js';
import type { Cloudly } from '../cloudly.classes.cloudly.js';
import { logger } from '../logger.js';
import type { Cloudly } from '../classes.cloudly.js';
/**
* The `ConfigVault` class provides methods for reading and writing configuration data to a file.
@@ -34,121 +34,143 @@ export class CloudlySecretManager {
// lets set up a typedrouter
this.typedrouter = new plugins.typedrequest.TypedRouter();
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>(
'adminLoginWithUsernameAndPassword',
async (dataArg) => {
let jwt: string;
// console.log(dataArg);
if (dataArg.username !== 'admin' || dataArg.password !== 'password') {
logger.log('warn', 'login failed');
} else {
jwt = await this.cloudlyRef.config.smartjwtInstance.createJWT({
status: 'loggedIn',
});
logger.log('success', 'login successful');
}
return {
jwt,
};
}
)
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
'adminGetConfigBundlesAndSecretGroups',
async (dataArg) => {
dataArg.jwt
// secretbundle routes
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetSecretBundles>(
new plugins.typedrequest.TypedHandler(
'getSecretBundles',
async (dataArg, toolsArg) => {
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], dataArg);
dataArg.identity.jwt;
const secretBundles = await SecretBundle.getInstances({});
const secretGroups = await SecretGroup.getInstances({});
return {
secretBundles: [
...(await Promise.all(
secretBundles.map((configBundle) => configBundle.createSavableObject())
secretBundles.map((configBundle) => configBundle.createSavableObject()),
)),
],
};
},
),
);
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_CreateSecretBundle>(
new plugins.typedrequest.TypedHandler('createSecretBundle', async (dataArg) => {
const secretBundle = new SecretBundle();
secretBundle.id = plugins.smartunique.shortId(8);
secretBundle.data = dataArg.secretBundle.data;
await secretBundle.save();
return {
resultSecretBundle: await secretBundle.createSavableObject(),
};
}),
);
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_UpdateSecretBundle>(
new plugins.typedrequest.TypedHandler('updateSecretBundle', async (dataArg) => {
const secretBundle = await SecretBundle.getInstance({
id: dataArg.secretBundle.id,
});
secretBundle.data = dataArg.secretBundle.data;
await secretBundle.save();
return {
resultSecretBundle: await secretBundle.createSavableObject(),
};
}),
);
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_DeleteSecretBundleById>(
new plugins.typedrequest.TypedHandler('deleteSecretBundleById', async (dataArg) => {
const secretBundle = await SecretBundle.getInstance({
id: dataArg.secretBundleId,
});
await secretBundle.delete();
return {
ok: true,
};
}),
);
// secretgroup routes
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretgroup.IReq_GetSecretGroups>(
new plugins.typedrequest.TypedHandler(
'getSecretGroups',
async (dataArg, toolsArg) => {
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], dataArg);
dataArg.identity.jwt;
const secretGroups = await SecretGroup.getInstances({});
return {
secretGroups: [
...(await Promise.all(
secretGroups.map((secretGroup) => secretGroup.createSavableObject())
secretGroups.map((secretGroup) => secretGroup.createSavableObject()),
)),
],
};
}
)
},
),
);
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_CreateConfigBundlesAndSecretGroups>(
new plugins.typedrequest.TypedHandler(
'adminCreateConfigBundlesAndSecretGroups',
async (dataArg) => {
for (const secretGroupObject of dataArg.secretGroups) {
const secretGroup = new SecretGroup();
secretGroup.id = plugins.smartunique.shortId(8);
secretGroup.data = secretGroupObject.data;
await secretGroup.save();
}
return {
ok: true,
};
}
)
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretgroup.IReq_CreateSecretGroup>(
new plugins.typedrequest.TypedHandler('createSecretGroup', async (dataArg) => {
const secretGroup = new SecretGroup();
secretGroup.id = plugins.smartunique.shortId(8);
secretGroup.data = dataArg.secretGroup.data;
await secretGroup.save();
return {
resultSecretGroup: await secretGroup.createSavableObject(),
};
}),
);
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretgroup.IReq_UpdateSecretGroup>(
new plugins.typedrequest.TypedHandler('updateSecretGroup', async (dataArg) => {
const secretGroup = await SecretGroup.getInstance({
id: dataArg.secretGroup.id,
});
secretGroup.data = dataArg.secretGroup.data;
await secretGroup.save();
return {
resultSecretGroup: await secretGroup.createSavableObject(),
};
}),
);
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretgroup.IReq_DeleteSecretGroupById>(
new plugins.typedrequest.TypedHandler('deleteSecretGroupById', async (dataArg) => {
const secretGroup = await SecretGroup.getInstance({
id: dataArg.secretGroupId,
});
await secretGroup.delete();
return {
ok: true,
};
}),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_DeleteConfigBundlesAndSecretGroups>(
'adminDeleteConfigBundlesAndSecretGroups',
async (dataArg) => {
for (const secretGroupId of dataArg.secretGroupIds) {
const secretGroup = await SecretGroup.getInstance({
id: secretGroupId,
});
await secretGroup.delete();
}
for (const secretBundleId of dataArg.secretBundleIds) {
const configBundle = await SecretBundle.getInstance({
id: secretBundleId,
});
await configBundle.delete();
console.log(`deleted configbundle ${secretBundleId}`);
}
return {
ok: true,
};
}
)
);
// lets add typedrouter routes for accessing the configvailt from apps
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_GetEnvBundle>(
'getEnvBundle',
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetFlatKeyValueObject>(
'getFlatKeyValueObject',
async (dataArg) => {
const wantedBundle = await SecretBundle.getInstance({
data: {
authorizations: {
// @ts-ignore
$elemMatch: {
secretAccessKey: dataArg.authorization,
secretAccessKey: dataArg.secretBundleAuthorization.secretAccessKey,
},
},
},
});
const authorization = await wantedBundle.getAuthorizationFromAuthKey(
dataArg.authorization
dataArg.secretBundleAuthorization.secretAccessKey,
);
return {
envBundle: {
configKeyValueObject: await wantedBundle.getKeyValueObjectForEnvironment(
authorization.environment
),
environment: authorization.environment,
timeSensitive: false,
},
flatKeyValueObject: await wantedBundle.getKeyValueObjectForEnvironment(
authorization.environment,
),
};
}
)
},
),
);
}

View File

@@ -0,0 +1,90 @@
import { logger } from '../logger.js';
import * as plugins from '../plugins.js';
import type { CloudlyServerManager } from './classes.servermanager.js';
export class CurlFresh {
public optionsArg = {
npmRegistry: 'https://registry.npmjs.org',
};
public scripts = {
'setup.sh': `#!/bin/bash
# lets update the system and install curl
# might be installed already, but entrypoint could have been wget
apt-get update
apt-get install -y --force-yes curl
# Basic updating of the software lists
echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
apt-get update
apt-get upgrade -y --force-yes
apt-get install -y --force-yes fail2ban curl git
curl -sL https://deb.nodesource.com/setup_18.x | bash
# Install docker
curl -sSL https://get.docker.com/ | sh
# Install default nodejs to run nodejs tools
apt-get install -y nodejs zsh
zsh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
npm config set unsafe-perm true
# lets install pnpm
curl -fsSL https://get.pnpm.io/install.sh | sh -
# lets make sure we use the correct npm registry
bash -c "npm config set registry ${this.optionsArg.npmRegistry}"
# lets install spark
bash -c "pnpm install -g @serve.zone/spark"
# lets install the spark daemon
bash -c "spark installdaemon"
# TODO: start spark with jump code
`,
};
public serverManagerRef: CloudlyServerManager;
public curlFreshRoute: plugins.typedserver.servertools.Route;
public handler = new plugins.typedserver.servertools.Handler('ALL', async (req, res) => {
logger.log('info', 'curlfresh handler called. a server might be coming online soon :)');
const scriptname = req.params.scriptname;
switch (scriptname) {
case 'setup.sh':
logger.log('info', 'sending setup.sh');
res.type('application/x-sh');
res.send(this.scripts['setup.sh']);
break;
default:
res.send('no script found');
break;
}
});
constructor(serverManagerRefArg: CloudlyServerManager) {
this.serverManagerRef = serverManagerRefArg;
}
public async getServerUserData(): Promise<string> {
const sslMode =
await this.serverManagerRef.cloudlyRef.config.appData.waitForAndGetKey('sslMode');
let protocol: 'http' | 'https';
if (sslMode === 'none') {
protocol = 'http';
} else {
protocol = 'https';
}
const domain =
await this.serverManagerRef.cloudlyRef.config.appData.waitForAndGetKey('publicUrl');
const port =
await this.serverManagerRef.cloudlyRef.config.appData.waitForAndGetKey('publicPort');
const serverUserData = `#cloud-config
runcmd:
- curl -o- ${protocol}://${domain}:${port}/curlfresh/setup.sh | sh
`;
console.log(serverUserData);
return serverUserData;
}
}

View File

@@ -1,13 +1,16 @@
import * as plugins from '../cloudly.plugins.js';
import * as plugins from '../plugins.js';
/*
* cluster defines a swarmkit cluster
*/
@plugins.smartdata.Manager()
export class Server extends plugins.smartdata.SmartDataDbDoc<Server, plugins.servezoneInterfaces.data.IServer> {
export class Server extends plugins.smartdata.SmartDataDbDoc<
Server,
plugins.servezoneInterfaces.data.IServer
> {
// STATIC
public static async createFromHetznerServer(
hetznerServerArg: plugins.hetznercloud.HetznerServer
hetznerServerArg: plugins.hetznercloud.HetznerServer,
) {
const newServer = new Server();
newServer.id = plugins.smartunique.shortId(8);
@@ -16,7 +19,7 @@ export class Server extends plugins.smartdata.SmartDataDbDoc<Server, plugins.ser
requiredDebianPackages: [],
sshKeys: [],
type: 'hetzner',
}
};
Object.assign(newServer, { data });
await newServer.save();
return newServer;

View File

@@ -1,11 +1,13 @@
import * as plugins from '../cloudly.plugins.js';
import { Cloudly } from '../cloudly.classes.cloudly.js';
import { Cluster } from '../manager.cluster/cluster.js';
import { Server } from './server.js';
import * as plugins from '../plugins.js';
import { Cloudly } from '../classes.cloudly.js';
import { Cluster } from '../manager.cluster/classes.cluster.js';
import { Server } from './classes.server.js';
import { CurlFresh } from './classes.curlfresh.js';
export class CloudlyServerManager {
public cloudlyRef: Cloudly;
public typedRouter = new plugins.typedrequest.TypedRouter();
public curlfreshInstance = new CurlFresh(this);
public hetznerAccount: plugins.hetznercloud.HetznerAccount;
@@ -27,17 +29,19 @@ export class CloudlyServerManager {
const serverId = requestData.serverId;
const server = await this.CServer.getInstance({
id: serverId,
})
});
return {
configData: await server.createSavableObject(),
};
}
)
},
),
);
}
public async start() {
this.hetznerAccount = new plugins.hetznercloud.HetznerAccount(this.cloudlyRef.config.data.hetznerToken);
this.hetznerAccount = new plugins.hetznercloud.HetznerAccount(
this.cloudlyRef.config.data.hetznerToken,
);
}
public async stop() {}
@@ -63,16 +67,19 @@ export class CloudlyServerManager {
labels: {
clusterId: cluster.id,
priority: '1',
}
},
userData: await this.curlfreshInstance.getServerUserData(),
});
const newServer = await Server.createFromHetznerServer(server);
console.log(`cluster created new server for cluster ${cluster.id}`);
} else {
console.log(`cluster ${cluster.id} already has servers. Making sure that they actually exist in the real world...`);
console.log(
`cluster ${cluster.id} already has servers. Making sure that they actually exist in the real world...`,
);
// if there is a server, make sure that it exists
for (const server of servers) {
const hetznerServer = await this.hetznerAccount.getServersByLabel({
'clusterId': cluster.id
clusterId: cluster.id,
});
if (!hetznerServer) {
console.log(`server ${server.id} does not exist in the real world. Creating it now...`);
@@ -83,7 +90,7 @@ export class CloudlyServerManager {
labels: {
clusterId: cluster.id,
priority: '1',
}
},
});
const newServer = await Server.createFromHetznerServer(hetznerServer);
}
@@ -96,7 +103,7 @@ export class CloudlyServerManager {
const results = await this.CServer.getInstances({
data: {
assignedClusterId: clusterArg.id,
}
},
});
return results;
}

View File

@@ -0,0 +1,57 @@
import { SecretBundle } from 'ts/manager.secret/classes.secretbundle.js';
import * as plugins from '../plugins.js';
import { ServiceManager } from './classes.servicemanager.js';
export class Service extends plugins.smartdata.SmartDataDbDoc<
Service,
plugins.servezoneInterfaces.data.IService,
ServiceManager
> {
// STATIC
public static async getServiceById(serviceIdArg: string) {
const service = await this.getInstance({
id: serviceIdArg,
});
return service;
}
public static async getServices() {
const services = await this.getInstances({});
return services;
}
public static async createService(serviceDataArg: Partial<plugins.servezoneInterfaces.data.IService['data']>) {
const service = new Service();
service.id = await Service.getNewId();
Object.assign(service, serviceDataArg);
await service.save();
return service;
}
// INSTANCE
@plugins.smartdata.svDb()
public id: string;
@plugins.smartdata.svDb()
public data: plugins.servezoneInterfaces.data.IService['data'];
/**
* a service runs in a specific environment
* so -> this method returns the secret bundles as a flat object accordingly.
* in other words, it resolves secret groups for the relevant environment
* @param environmentArg
*/
public async getSecretBundlesAsFlatObject(environmentArg: string = 'production') {
const secreBundleIds = this.data.additionalSecretBundleIds || [];
secreBundleIds.push(this.data.secretBundleId); // put this last, so it overwrites any other secret bundles.
let finalFlatObject = {};
for (const secretBundleId of secreBundleIds) {
const secretBundle = await SecretBundle.getInstance({
id: secretBundleId,
});
const flatObject = await secretBundle.getFlatKeyValueObject(environmentArg);
Object.assign(finalFlatObject, flatObject);
}
return finalFlatObject;
}
}

View File

@@ -0,0 +1,100 @@
import type { Cloudly } from '../classes.cloudly.js';
import * as plugins from '../plugins.js';
import { Service } from './classes.service.js';
export class ServiceManager {
public typedrouter = new plugins.typedrequest.TypedRouter();
public cloudlyRef: Cloudly;
get db() {
return this.cloudlyRef.mongodbConnector.smartdataDb;
}
public CService = plugins.smartdata.setDefaultManagerForDoc(this, Service);
constructor(cloudlyRef: Cloudly) {
this.cloudlyRef = cloudlyRef;
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_GetServices>(
'getServices',
async (reqArg) => {
await plugins.smartguard.passGuardsOrReject(reqArg, [
this.cloudlyRef.authManager.validIdentityGuard,
]);
const services = await this.CService.getInstances({});
return {
services: await Promise.all(
services.map((service) => {
return service.createSavableObject();
})
),
};
}
)
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_GetServiceSecretBundlesAsFlatObject>(
'getServiceSecretBundlesAsFlatObject',
async (dataArg) => {
const service = await Service.getInstance({
id: dataArg.serviceId,
});
const flatKeyValueObject = await service.getSecretBundlesAsFlatObject(dataArg.environment);
return {
flatKeyValueObject: flatKeyValueObject,
};
}
)
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_CreateService>(
'createService',
async (dataArg) => {
const service = await Service.createService(dataArg.serviceData);
return {
service: await service.createSavableObject(),
};
}
)
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_UpdateService>(
'updateService',
async (dataArg) => {
const service = await Service.getInstance({
id: dataArg.serviceId,
});
service.data = {
...service.data,
...dataArg.serviceData,
};
await service.save();
return {
service: await service.createSavableObject(),
};
}
)
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.service.IRequest_Any_Cloudly_DeleteServiceById>(
'deleteServiceById',
async (dataArg) => {
const service = await Service.getInstance({
id: dataArg.serviceId,
});
await service.delete();
return {
success: true,
};
}
)
);
}
}

View File

@@ -1,4 +1,4 @@
import * as plugins from '../cloudly.plugins.js';
import * as plugins from '../plugins.js';
import { Cloudly } from '../index.js';
/**
@@ -16,7 +16,7 @@ export class ExternalApiManager {
return {
networkNodes,
};
})
}),
);
}
}

View File

@@ -1,7 +1,7 @@
import * as plugins from '../cloudly.plugins.js';
import { Cloudly } from '../cloudly.classes.cloudly.js';
import * as plugins from '../plugins.js';
import { Cloudly } from '../classes.cloudly.js';
import { logger } from '../cloudly.logging.js';
import { logger } from '../logger.js';
export class CloudlyTaskmanager {
public cloudlyRef: Cloudly;

View File

@@ -1,32 +0,0 @@
import * as plugins from '../cloudly.plugins.js';
/*
A container version is managed by the versionmanager
*/
@plugins.smartdata.Manager()
export class ContainerVersion
extends plugins.smartdata.SmartDataDbDoc<ContainerVersion, unknown>
implements plugins.servezoneInterfaces.data.IContainerVersionData
{
public static async fromIVersionData(
dataArg: plugins.servezoneInterfaces.data.IContainerVersionData
) {
const containerVersionInstance = new ContainerVersion();
containerVersionInstance.id = plugins.smartunique.shortId();
Object.assign(containerVersionInstance, dataArg);
return containerVersionInstance;
}
@plugins.smartdata.unI()
public id: string;
@plugins.smartdata.svDb()
public dockerImageUrl: string;
@plugins.smartdata.svDb()
public dockerImageVersion: string;
constructor() {
super();
}
}

View File

@@ -1,149 +0,0 @@
import * as plugins from '../cloudly.plugins.js';
import { ContainerVersion } from './containerversion.js';
import { Cloudly } from '../cloudly.classes.cloudly.js';
export class CloudlyVersionManager {
// INSTANCE
public cloudlyRef: Cloudly;
public get db() {
return this.cloudlyRef.mongodbConnector.smartdataDb;
}
public typedRouter = new plugins.typedrequest.TypedRouter();
// connected classes
public CContainerVersion = plugins.smartdata.setDefaultManagerForDoc(this, ContainerVersion);
constructor(cloudlyRefArg: Cloudly) {
this.cloudlyRef = cloudlyRefArg;
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
// get version
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.version.IRequest_Any_Cloudly_VersionManager_GetLatestContainerVersion>(
new plugins.typedrequest.TypedHandler(
'getLatestContainerVersion',
async (typedRequestData) => {
const containerVersionGet: ContainerVersion =
await ContainerVersion.getInstance<ContainerVersion>({
dockerImageUrl: typedRequestData.dockerImageUrl,
});
return {
dockerImageUrl: containerVersionGet.dockerImageUrl,
dockerImageVersion: containerVersionGet.dockerImageVersion,
};
}
)
);
// update version
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.version.IRequest_Any_Cloudly_VersionManager_InformCloudlyAboutNewContainerVersion>(
new plugins.typedrequest.TypedHandler(
'informCloudlyAboutNewContainerVersion',
async (dataArg) => {
console.log(`Got a container version announcement! "${dataArg.dockerImageUrl}"`);
let containerVersion: ContainerVersion =
await ContainerVersion.getInstance<ContainerVersion>({
dockerImageUrl: dataArg.dockerImageUrl,
});
if (containerVersion) {
containerVersion.dockerImageVersion = dataArg.dockerImageVersion;
await containerVersion.save();
} else {
containerVersion = await ContainerVersion.fromIVersionData(dataArg);
await containerVersion.save();
}
// lets push this info to the relevant clusters
const clusters = await this.cloudlyRef.clusterManager.getAllClusters();
let foundServices: plugins.servezoneInterfaces.data.IService;
let relevantClusterIdentifier: plugins.servezoneInterfaces.data.IClusterIdentifier;
for (const clusterArg of clusters) {
console.log(clusterArg);
for (const serviceArg of await clusterArg.getServices()) {
if (serviceArg.image === containerVersion.dockerImageUrl) {
foundServices = serviceArg;
break;
}
}
if (foundServices) {
relevantClusterIdentifier = {
clusterName: clusterArg.data.name,
secretKey: clusterArg.data.secretKey,
};
break;
}
}
if (!relevantClusterIdentifier) {
console.log('no cluster found that needs to update');
return {};
} else {
console.log('found relevant cluster identifier:');
console.log(relevantClusterIdentifier);
}
const targetConnection =
await this.cloudlyRef.server.typedsocketServer.findTargetConnection(
async (connectionArg) => {
const identityTag = await connectionArg.getTagById('identity');
if (!identityTag) {
return false;
}
const result =
plugins.smartjson.stringify(identityTag.payload) ===
plugins.smartjson.stringify(relevantClusterIdentifier);
return result;
}
);
if (targetConnection) {
console.log(`the relevant cluster is connected and is now being informed.`);
const informCoreflowTR =
this.cloudlyRef.server.typedsocketServer.createTypedRequest<plugins.servezoneInterfaces.requests.version.IRequest_Cloudly_Coreflow_VersionManager_InformCoreflowAboutNewContainerVersion>(
'informCoreflowAboutNewContainerVersion',
targetConnection
);
informCoreflowTR.fire({
dockerImageUrl: containerVersion.dockerImageUrl,
dockerImageVersion: containerVersion.dockerImageVersion,
});
} else {
console.log('the relevant cluster is not connected at this time.');
}
return {};
}
)
);
// lets support the servezone standard
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.IRequest_InformAboutNewContainerImage>(
'servezonestandard_InformAboutNewContainerVersion',
async (dataArg) => {
const result =
await this.typedRouter.routeAndAddResponse<plugins.servezoneInterfaces.requests.version.IRequest_Any_Cloudly_VersionManager_InformCloudlyAboutNewContainerVersion>(
{
method: 'informCloudlyAboutNewContainerVersion',
request: {
dockerImageUrl: dataArg.containerImageInfo.registryUrl,
dockerImageVersion: dataArg.containerImageInfo.version,
},
response: null
},
true
);
return result.response;
}
)
);
}
/**
* gets all versions
*/
public async getAllVersions() {
const result = await ContainerVersion.getInstances<ContainerVersion>({});
return result;
}
}

9
ts/paths.ts Normal file
View File

@@ -0,0 +1,9 @@
import * as plugins from './plugins.js';
export const packageDir = plugins.path.join(
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
'../',
);
export const nogitDir = plugins.path.join(packageDir, '.nogit/');
export const dockerImageStoreDir = plugins.path.join(nogitDir, './dockerimagestore/');
export const distServeDir = plugins.path.join(packageDir, './dist_serve');

View File

@@ -9,13 +9,13 @@ import * as typedsocket from '@api.global/typedsocket';
export { typedrequest, typedsocket };
// @mojoio scope
// @apiclient.xyz scope
import * as cloudflare from '@apiclient.xyz/cloudflare';
import * as digitalocean from '@apiclient.xyz/digitalocean';
import * as docker from '@apiclient.xyz/docker';
import * as hetznercloud from '@apiclient.xyz/hetznercloud';
import * as slack from '@apiclient.xyz/slack';
export { cloudflare, digitalocean, hetznercloud, slack };
export { cloudflare, docker, hetznercloud, slack };
// @tsclass scope
import * as tsclass from '@tsclass/tsclass';
@@ -29,10 +29,11 @@ import * as qenv from '@push.rocks/qenv';
import * as smartacme from '@push.rocks/smartacme';
import * as smartbucket from '@push.rocks/smartbucket';
import * as smartcli from '@push.rocks/smartcli';
import * as smartclickhouse from '@push.rocks/smartclickhouse';
import * as smartdata from '@push.rocks/smartdata';
import * as smartdelay from '@push.rocks/smartdelay';
import * as smartexit from '@push.rocks/smartexit';
import * as typedserver from '@api.global/typedserver';
import * as smartexpect from '@push.rocks/smartexpect';
import * as smartfile from '@push.rocks/smartfile';
import * as smartguard from '@push.rocks/smartguard';
import * as smartjson from '@push.rocks/smartjson';
@@ -42,9 +43,11 @@ import * as smartpath from '@push.rocks/smartpath';
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrequest from '@push.rocks/smartrequest';
import * as smartssh from '@push.rocks/smartssh';
import * as smartstream from '@push.rocks/smartstream';
import * as smartstring from '@push.rocks/smartstring';
import * as smartunique from '@push.rocks/smartunique';
import * as taskbuffer from '@push.rocks/taskbuffer';
import * as typedserver from '@api.global/typedserver';
export {
npmextra,
@@ -53,9 +56,10 @@ export {
smartacme,
smartbucket,
smartcli,
smartclickhouse,
smartdata,
smartexit,
typedserver,
smartexpect,
smartdelay,
smartfile,
smartguard,
@@ -66,9 +70,11 @@ export {
smartpromise,
smartrequest,
smartssh,
smartstream,
smartstring,
smartunique,
taskbuffer,
typedserver,
};
// @servezone scope

View File

@@ -0,0 +1,240 @@
import * as plugins from './plugins.js';
export type TClientType = 'api' | 'ci' | 'coreflow' | 'cli' | 'serverconfig';
import { Image } from './classes.image.js';
import { Service } from './classes.service.js';
import { Cluster } from './classes.cluster.js';
import { SecretBundle } from './classes.secretbundle.js';
import { SecretGroup } from './classes.secretgroup.js';
import { ExternalRegistry } from './classes.externalregistry.js';
export class CloudlyApiClient {
private cloudlyUrl: string;
private registerAs: string;
public typedrouter = new plugins.typedrequest.TypedRouter();
public typedsocketClient: plugins.typedsocket.TypedSocket;
// Subjects
public configUpdateSubject = new plugins.smartrx.rxjs.Subject<
plugins.servezoneInterfaces.requests.config.IRequest_Cloudly_Coreflow_PushClusterConfig['request']
>();
public serverActionSubject = new plugins.smartrx.rxjs.Subject<
plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction['request']
>();
constructor(optionsArg: {
registerAs: TClientType;
cloudlyUrl?: string;
}) {
this.registerAs = optionsArg.registerAs;
this.cloudlyUrl =
optionsArg?.cloudlyUrl || process.env.CLOUDLY_URL || 'https://cloudly.layer.io:443';
console.log(
`creating LoleCloudlyClient: registering as ${this.registerAs} and target url ${this.cloudlyUrl}`
);
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Cloudly_Coreflow_PushClusterConfig>(
new plugins.typedrequest.TypedHandler('pushClusterConfig', async (dataArg) => {
this.configUpdateSubject.next(dataArg);
return {};
})
);
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction>(
new plugins.typedrequest.TypedHandler('triggerServerAction', async (dataArg) => {
this.serverActionSubject.next(dataArg);
return {
actionConfirmed: true,
};
})
);
}
public async start() {
this.typedsocketClient = await plugins.typedsocket.TypedSocket.createClient(
this.typedrouter,
this.cloudlyUrl
);
console.log(
`CloudlyClient connected to cloudly at ${this.cloudlyUrl}. Remember to get an identity.`
);
}
public async stop() {
await this.typedsocketClient.stop();
}
public identity: plugins.servezoneInterfaces.data.IIdentity;
public async getIdentityByToken(
token: string,
optionsArg?: {
tagConnection?: boolean;
statefullIdentity?: boolean;
}
): Promise<plugins.servezoneInterfaces.data.IIdentity> {
optionsArg = Object.assign({}, {
tagConnection: false,
statefullIdentity: true,
}, optionsArg);
const identityRequest =
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken>(
'getIdentityByToken'
);
console.log(`trying to get identity from cloudly with supplied jumpCodeArg: ${token}`);
const response = await identityRequest.fire({
token: token,
});
console.log('got identity response');
const identity = response.identity;
if (optionsArg.tagConnection) {
this.typedsocketClient.addTag('identity', identity);
}
if (optionsArg.statefullIdentity) {
this.identity = identity;
}
return identity;
}
/**
* will use statefull identity by default
*/
public async getClusterConfigFromCloudlyByIdentity(
identityArg: plugins.servezoneInterfaces.data.IIdentity = this.identity
): Promise<plugins.servezoneInterfaces.data.ICluster> {
const clusterConfigRequest =
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>(
'getClusterConfig'
);
const response = await clusterConfigRequest.fire({
identity: identityArg,
});
return response.configData;
}
/**
* will use statefull identity by default
*/
public async getServerConfigFromCloudlyByIdentity(
identityArg: plugins.servezoneInterfaces.data.IIdentity = this.identity
): Promise<plugins.servezoneInterfaces.data.IServer> {
const serverConfigRequest =
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetServerConfig>(
'getServerConfig'
);
const response = await serverConfigRequest.fire({
identity: identityArg,
serverId: '', // TODO: get server id here
});
return response.configData;
}
/**
* gets a certificate for a domain used by a service
*/
public async getCertificateForDomain(optionsArg: {
domainName: string;
type: plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain['request']['type'];
identity?: plugins.servezoneInterfaces.data.IIdentity;
}): Promise<plugins.tsclass.network.ICert> {
optionsArg.identity = optionsArg.identity || this.identity;
if (!optionsArg.identity) {
throw new Error('identity is required. Either provide one or login first.');
}
const typedCertificateRequest =
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain>(
'getCertificateForDomain'
);
const typedResponse = await typedCertificateRequest.fire({
identity: this.identity, // do proper auth here
domainName: optionsArg.domainName,
type: optionsArg.type,
});
return typedResponse.certificate;
}
public externalRegistry = {
// ExternalRegistry
getRegistryById: async (registryNameArg: string) => {
return ExternalRegistry.getExternalRegistryById(this, registryNameArg);
},
getRegistries: async () => {
return ExternalRegistry.getExternalRegistries(this);
},
createRegistry: async (optionsArg: Parameters<typeof ExternalRegistry.createExternalRegistry>[1]) => {
return ExternalRegistry.createExternalRegistry(this, optionsArg);
}
}
public image = {
// Images
getImageById: async (imageIdArg: string) => {
return Image.getImageById(this, imageIdArg);
},
getImages: async () => {
return Image.getImages(this);
},
createImage: async (optionsArg: Parameters<typeof Image.createImage>[1]) => {
return Image.createImage(this, optionsArg);
}
}
public services = {
// Services
getServiceById: async (serviceIdArg: string) => {
return Service.getServiceById(this, serviceIdArg);
},
getServices: async () => {
return Service.getServices(this);
},
createService: async (optionsArg: Parameters<typeof Service.createService>[1]) => {
return Service.createService(this, optionsArg);
}
}
public cluster = {
// Clusters
getClusterById: async (clusterIdArg: string) => {
return Cluster.getClusterById(this, clusterIdArg);
},
getClusters: async () => {
return Cluster.getClusters(this);
},
createCluster: async (optionsArg: Parameters<typeof Cluster.createCluster>[1]) => {
return Cluster.createCluster(this, optionsArg);
}
}
public secretbundle = {
// SecretBundles
getSecretBundleById: async (secretBundleIdArg: string) => {
return SecretBundle.getSecretBundleById(this, secretBundleIdArg);
},
getSecretBundles: async () => {
return SecretBundle.getSecretBundles(this);
},
createSecretBundle: async (optionsArg: Parameters<typeof SecretBundle.createSecretBundle>[1]) => {
return SecretBundle.createSecretBundle(this, optionsArg);
}
}
public secretgroup = {
// SecretGroups
getSecretGroupById: async (secretGroupIdArg: string) => {
return SecretGroup.getSecretGroupById(this, secretGroupIdArg);
},
getSecretGroups: async () => {
return SecretGroup.getSecretGroups(this);
},
createSecretGroup: async (optionsArg: Parameters<typeof SecretGroup.createSecretGroup>[1]) => {
return SecretGroup.createSecretGroup(this, optionsArg);
}
}
}

View File

@@ -1,13 +0,0 @@
import * as plugins from './plugins.js';
export class CloudlyClient {
public clientToken: string;
constructor(clientToken: string) {
this.clientToken = clientToken;
}
public async getClusters() {
}
}

View File

@@ -1,5 +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

@@ -1,5 +1,113 @@
import type { CloudlyApiClient } from './classes.cloudlyapiclient.js';
import * as plugins from './plugins.js';
export class Image {
public getImages() {}
}
export class Image implements plugins.servezoneInterfaces.data.IImage {
public static async getImages(cloudlyClientRef: CloudlyApiClient) {
const getAllImagesTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
'getAllImages'
);
const response = await getAllImagesTR.fire({
identity: cloudlyClientRef.identity,
});
const resultImages: Image[] = [];
for (const image of response.images) {
const newImage = new Image(cloudlyClientRef);
Object.assign(newImage, image);
resultImages.push(newImage);
}
return resultImages;
}
public static async getImageById(cloudlyClientRef: CloudlyApiClient, imageIdArg: string) {
const getImageByIdTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_GetImage>(
'getImage'
);
const response = await getImageByIdTR.fire({
identity: cloudlyClientRef.identity,
imageId: imageIdArg,
});
const newImage = new Image(cloudlyClientRef);
Object.assign(newImage, response.image);
return newImage;
}
/**
* creates a new image
*/
public static async createImage(cloudlyClientRef: CloudlyApiClient, imageDataArg: Partial<plugins.servezoneInterfaces.data.IImage['data']>) {
const createImageTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
'createImage'
);
const response = await createImageTR.fire({
identity: cloudlyClientRef.identity,
name: imageDataArg.name,
description: imageDataArg.description,
});
const newImage = new Image(cloudlyClientRef);
Object.assign(newImage, response.image);
return newImage;
}
// INSTANCE
cloudlyClientRef: CloudlyApiClient;
id: plugins.servezoneInterfaces.data.IImage['id'];
data: plugins.servezoneInterfaces.data.IImage['data'];
constructor(cloudlyClientRef: CloudlyApiClient) {
this.cloudlyClientRef = cloudlyClientRef;
}
/**
* updates the image data
*/
public async update() {
const getVersionsTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_GetImage>(
'getImage'
);
const response = await getVersionsTR.fire({
identity: this.cloudlyClientRef.identity,
imageId: this.id,
});
Object.assign(this, response.image);
}
/**
* pushes a new version of the image
* @param imageVersion
* @param imageReadableArg
*/
public async pushImageVersion(imageVersion: string, imageReadableArg: ReadableStream<Uint8Array>): Promise<void> {
const done = plugins.smartpromise.defer();
const pushImageTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_PushImageVersion>(
'pushImageVersion'
);
const virtualStream = new plugins.typedrequest.VirtualStream();
const response = await pushImageTR.fire({
identity: this.cloudlyClientRef.identity,
imageId: this.id,
versionString: '',
imageStream: virtualStream,
});
await virtualStream.readFromWebstream(imageReadableArg);
await this.update();
};
/**
* pulls a version of the image
*/
public async pullImageVersion(versionStringArg: string): Promise<ReadableStream<Uint8Array>> {
const pullImageTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_PullImageVersion>(
'pullImageVersion'
);
const response = await pullImageTR.fire({
identity: this.cloudlyClientRef.identity,
imageId: this.id,
versionString: versionStringArg,
});
const imageStream = response.imageStream;
const webduplexStream = new plugins.webstream.WebDuplexStream({});
imageStream.writeToWebstream(webduplexStream.writable);
return webduplexStream.readable;
};
}

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

@@ -0,0 +1,7 @@
import * as plugins from './plugins.js';
export class Server {
public static getServers() {
}
}

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

@@ -1 +1 @@
export * from './classes.cloudlyclient.js';
export * from './classes.cloudlyapiclient.js';

View File

@@ -1,5 +1,35 @@
import * as typedrequest from '@api.global/typedrequest';
// @serve.zone scope
import * as servezoneInterfaces from '@serve.zone/interfaces';
export {
typedrequest
servezoneInterfaces
}
// @push.rocks scope
import * as smartexpect from '@push.rocks/smartexpect';
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrx from '@push.rocks/smartrx';
import * as webstream from '@push.rocks/smartstream/web';
export {
smartexpect,
smartpromise,
smartrx,
webstream,
}
// @api.global scope
import * as typedrequest from '@api.global/typedrequest';
import * as typedsocket from '@api.global/typedsocket';
export {
typedrequest,
typedsocket
}
// @tsclass scope
import * as tsclass from '@tsclass/tsclass';
export {
tsclass,
}

306
ts_apiclient/readme.md Normal file
View File

@@ -0,0 +1,306 @@
# @serve.zone/api 🔌
**The powerful API client for Cloudly.** Connect your applications to multi-cloud infrastructure with type-safe, real-time communication.
## 🎯 What is @serve.zone/api?
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
pnpm add @serve.zone/api
```
## 🎬 Quick Start
```typescript
import { CloudlyApiClient } from '@serve.zone/api';
// Initialize the client
const client = new CloudlyApiClient({
registerAs: 'api',
cloudlyUrl: 'https://cloudly.example.com:443'
});
// Start the connection
await client.start();
// Authenticate with a service token
const identity = await client.getIdentityByToken('your-service-token', {
tagConnection: true,
statefullIdentity: true
});
console.log('🎉 Connected as:', identity.name);
```
## 🔐 Authentication
### Service Token Authentication
```typescript
// 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
});
```
### Identity Management
```typescript
// Get current identity
const currentIdentity = client.identity;
// Check permissions
if (currentIdentity.permissions.includes('cluster:write')) {
// Perform cluster operations
}
```
## 📚 Core Operations
### 🐳 Image Management
```typescript
// Create an image entry
const image = await client.images.createImage({
name: 'my-app',
description: 'Production application image'
});
// Push a new version
const imageStream = fs.createReadStream('app.tar');
await image.pushImageVersion('2.0.0', imageStream);
// List all images
const images = await client.images.listImages();
```
### 🌐 Cluster Operations
```typescript
// Get cluster configuration
const clusterConfig = await client.getClusterConfigFromCloudlyByIdentity(identity);
// Deploy to cluster
await client.deployToCluster({
clusterName: 'production',
serviceName: 'api-service',
image: 'my-app:2.0.0',
replicas: 3
});
```
### 🔒 Certificate Management
```typescript
// Request SSL certificate
const certificate = await client.getCertificateForDomain({
domainName: 'api.example.com',
type: 'ssl',
identity: identity
});
// Use certificate in your application
console.log('Certificate:', certificate.cert);
console.log('Private Key:', certificate.key);
```
### 🔐 Secret Management
```typescript
// Create secret group
const secretGroup = await client.secrets.createSecretGroup({
name: 'api-secrets',
secrets: [
{ key: 'DATABASE_URL', value: 'postgres://...' },
{ key: 'REDIS_URL', value: 'redis://...' }
]
});
// Retrieve secrets
const secrets = await client.secrets.getSecretGroup('api-secrets');
```
## 🔄 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
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
### Company Information
Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

View File

@@ -0,0 +1,17 @@
{
"order": 1,
"name": "@serve.zone/api",
"dependencies": [
"@serve.zone/interfaces",
"@push.rocks/smartpromise",
"@push.rocks/smartrx",
"@push.rocks/smartstream",
"@api.global/typedrequest",
"@api.global/typedsocket",
"@tsclass/tsclass"
],
"registries": [
"registry.npmjs.org:public",
"verdaccio.lossless.digital:public"
]
}

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

@@ -0,0 +1,11 @@
import * as plugins from './plugins.js';
import { CliClient } from "./classes.cliclient.js";
export const runCli = async () => {
const cliQenv = new plugins.qenv.Qenv();
const apiClient = new plugins.servezoneApi.CloudlyApiClient({
registerAs: 'cli',
cloudlyUrl: await cliQenv.getEnvVarOnDemand('CLOUDLY_URL'),
});
const cliClient = new CliClient(apiClient);
};

17
ts_cliclient/plugins.ts Normal file
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,
}

358
ts_cliclient/readme.md Normal file
View File

@@ -0,0 +1,358 @@
# @serve.zone/cli 🚀
**Command-line interface for Cloudly.** Manage your multi-cloud infrastructure from the terminal with powerful, intuitive commands.
## 🎯 What is @serve.zone/cli?
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
pnpm add -g @serve.zone/cli
```
### Local Installation
```bash
pnpm add @serve.zone/cli
```
## 🎬 Quick Start
```bash
# Configure your Cloudly instance
servezone config --url https://cloudly.example.com
# Login with your service token
servezone login --token your-service-token
# List your clusters
servezone clusters list
# Deploy a service
servezone deploy --cluster production --image myapp:latest
```
## 🔑 Authentication
### Initial Setup
```bash
# Set your Cloudly instance URL
servezone config --url https://cloudly.example.com
# Authenticate with a service token
servezone login --token YOUR_SERVICE_TOKEN
# Or use environment variables
export CLOUDLY_URL=https://cloudly.example.com
export CLOUDLY_TOKEN=YOUR_SERVICE_TOKEN
```
### Managing Profiles
```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
# Switch between profiles
servezone profile use production
# List all profiles
servezone profile list
```
## 📚 Core Commands
### 🌐 Cluster Management
```bash
# List all clusters
servezone clusters list
# Get cluster details
servezone clusters info production-cluster
# Create a new cluster
servezone clusters create \
--name production-cluster \
--region eu-central \
--nodes 3
# Scale a cluster
servezone clusters scale production-cluster --nodes 5
# Delete a cluster
servezone clusters delete staging-cluster
```
### 🐳 Service Deployment
```bash
# Deploy a service
servezone deploy \
--cluster production \
--name api-service \
--image myapp:2.0.0 \
--replicas 3 \
--port 80:3000
# Update a service
servezone service update api-service \
--image myapp:2.1.0 \
--replicas 5
# Scale a service
servezone service scale api-service --replicas 10
# Remove a service
servezone service remove api-service
```
### 🔐 Secret Management
```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
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
### Company Information
Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

View File

@@ -0,0 +1,15 @@
{
"name": "@serve.zone/cli",
"dependencies": [
"@serve.zone/api",
"@serve.zone/interfaces",
"@push.rocks/projectinfo",
"@push.rocks/qenv",
"@push.rocks/smartcli"
],
"registries": [
"registry.npmjs.org:public",
"verdaccio.lossless.digital:public"
],
"bin": ["servezone"]
}

View File

@@ -0,0 +1,8 @@
/**
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
name: '@serve.zone/interfaces',
version: '1.1.2',
description: 'interfaces for working with containers'
}

View File

@@ -0,0 +1,16 @@
import * as plugins from '../plugins.js';
export interface ICloudlyConfig {
cfToken?: string;
hetznerToken?: string;
environment?: 'production' | 'integration';
letsEncryptEmail?: string;
letsEncryptPrivateKey?: string;
jwtKeypair?: plugins.tsclass.network.IJwtKeypair;
mongoDescriptor?: plugins.tsclass.database.IMongoDescriptor;
s3Descriptor?: plugins.tsclass.storage.IS3Descriptor;
publicUrl?: string;
publicPort?: string;
sslMode?: 'none' | 'letsencrypt' | 'external';
servezoneAdminaccount?: string;
}

View File

@@ -0,0 +1,36 @@
import * as plugins from '../plugins.js';
import { type IDockerRegistryInfo } from '../data/docker.js';
import type { IServer } from './server.js';
export interface ICluster {
id: string;
data: {
name: string;
/**
* a cluster has a machine user that governs access rights.
*/
userId: string;
/**
* how can the cluster reach cloudly
*/
cloudlyUrl?: string;
/**
* what servers are expected to be part of the cluster
*/
servers: IServer[];
/**
* ACME info. This is used to get SSL certificates.
*/
acmeInfo: {
serverAddress: string;
serverSecret: string;
};
sshKeys: plugins.tsclass.network.ISshKey[];
};
}

View File

@@ -0,0 +1 @@
export type TConfigType = 'server' | 'cluster' | 'coreflow' | 'service';

View File

@@ -0,0 +1,13 @@
import * as plugins from '../plugins.js';
/**
* a deployment happens when a service is deployed
* tracks the status of a deployment
*/
export interface IDeployment {
id: string;
affectedServiceIds: string[];
usedImageId: string;
deploymentLog: string[];
status: 'scheduled' | 'running' | 'deployed' | 'failed';
}

View File

@@ -0,0 +1,15 @@
import * as plugins from '../plugins.js';
export interface IDockerRegistryInfo {
serveraddress: string;
username: string;
password: string;
}
export interface IServiceRessources {
cpuLimit?: number;
cpuReservation?: number;
memorySizeLimitMB?: number;
memorySizeReservationMB?: number;
volumeMounts?: plugins.tsclass.container.IVolumeMount[];
}

View File

@@ -0,0 +1,11 @@
import * as plugins from '../plugins.js';
export interface IEvent_Cloudly_ContainerVersionNotification
extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedEvent<plugins.tsclass.container.IContainer>,
IEvent_Cloudly_ContainerVersionNotification
> {
name: 'newContainerVersion';
uniqueEventId: string;
payload: plugins.tsclass.container.IContainer;
}

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

@@ -0,0 +1,20 @@
import * as plugins from '../plugins.js';
export interface IImage {
id: string;
data: {
name: string;
location: {
internal: boolean;
externalRegistryId: string;
externalImageTag: string;
}
description: string;
versions: Array<{
versionString: string;
storagePath?: string;
size: number;
createdAt: number;
}>;
};
}

View File

@@ -0,0 +1,16 @@
export * from './cloudlyconfig.js';
export * from './cluster.js';
export * from './config.js';
export * from './deployment.js';
export * from './docker.js';
export * from './event.js';
export * from './externalregistry.js';
export * from './image.js';
export * from './secretbundle.js';
export * from './secretgroup.js'
export * from './server.js';
export * from './service.js';
export * from './status.js';
export * from './traffic.js';
export * from './user.js';
export * from './version.js';

View File

@@ -0,0 +1,61 @@
export interface ISecretBundle {
id: string;
data: {
name: string;
description: string;
/**
* determines if the secret is a service or an external secret
* if external secret additional checks are put in place to protect the secret
*
* * service:
* the bundle belongs to a service and can only be used by that service
* * npmci:
* the bundle is a secret bundle that is used by an npmci pipeline
* production secrets will be omitted in any case
* * gitzone:
* the bundle is a secret bundle that is used by a gitzone.
* Only local environment variables are allowed
* * external:
* the bundle is a secret bundle that is used by an external service
*/
type: 'service' | 'npmci' | 'gitzone' | 'external';
/**
* set this if the secretBundle belongs to a service
*/
serviceId?: string;
/**
* You can add specific secret groups using this
*/
includedSecretGroupIds: string[];
/**
* access to this secretBundle also grants access to resources with matching tags
*/
includedTags: {
key: string;
value?: string;
}[];
/**
* access to this secretBundle also grants access to the images
*/
imageClaims: {
imageId: string;
permissions: ('read' | 'write')[];
}[];
/**
* authrozations select a specific environment of a config bundle
*/
authorizations: Array<ISecretBundleAuthorization>;
};
}
export interface ISecretBundleAuthorization {
secretAccessKey: string;
environment: string;
}

View File

@@ -0,0 +1,54 @@
export interface ISecretGroup {
/**
* the insatnce id. This should be a random id, except for default
*/
id: string;
data: {
name: string;
description: string;
/**
* the key of the secretgroup like CI_RUNNER_TOKEN
*/
key: string;
/**
* the priority of the secretgroup
* will be used to determine which secretgroup will be used
* when there are multiple secretgroups with the same key
*/
priority?: number;
/**
* any tags that can be used to filter the secretgroup
* can be used for putting secrets into projects
*/
tags: {
key: string;
value: string;
}[];
/**
* the values for this secretGroup
*/
environments: {
[key: string]: {
value: string;
/**
* can be used to update the value
*/
updateToken?: string;
/**
* the linux timestamp of the last update
*/
lastUpdated: number;
history: {
timestamp: string;
value: string;
}[];
};
};
};
}

View File

@@ -0,0 +1,36 @@
import * as plugins from '../plugins.js';
import { type IDockerRegistryInfo } from './docker.js';
export interface IServerMetrics {
serverId: string;
cpuUsageInPercent: number;
memoryUsageinMB: number;
memoryAvailableInMB: number;
containerCount: number;
containerMetrics: Array<{
containerId: string;
containerName: string;
cpuUsageInPercent: number;
memoryUsageInMB: number;
}>;
}
export interface IServer {
id: string;
data: {
type: 'baremetal' | 'hetzner';
assignedClusterId: string;
/**
* a list of debian packages to be installed
*/
requiredDebianPackages: string[];
/**
* a list of SSH keys to deploy
*/
sshKeys: plugins.tsclass.network.ISshKey[];
};
}

View File

@@ -0,0 +1,34 @@
import type { IServiceRessources } from './docker.js';
export interface IService {
id: string;
data: {
name: string;
description: string;
imageId: string;
imageVersion: string;
environment: { [key: string]: string };
/**
* the main secret bundle id, exclusive to the service
*/
secretBundleId: string;
/**
* those secret bundle ids do not belong to the service itself
* and thus live past the service lifecycle
*/
additionalSecretBundleIds?: string[];
scaleFactor: number;
balancingStrategy: 'round-robin' | 'least-connections';
ports: {
web: number;
custom?: { [domain: string]: string };
};
resources?: IServiceRessources;
domains: {
name: string;
port?: number;
protocol?: 'http' | 'https' | 'ssh';
}[];
deploymentIds: string[];
};
}

View File

@@ -0,0 +1,20 @@
import * as plugins from '../plugins.js';
export interface IClusterStatus {
name: string;
ip: string;
nodesCount: number;
containersUnderManagementCount: number;
nodeStatusId: string;
containerStatusArray: IContainerStatus[];
}
export interface INodeStatus {
nodeId: string;
}
export interface IContainerStatus {
serviceName: string;
dockerImageUrl: string;
dockerImageVersion: string;
}

View File

@@ -0,0 +1,5 @@
import * as plugins from '../plugins.js';
interface IReverseProxyConfig extends plugins.tsclass.network.IReverseProxyConfig {}
export { type IReverseProxyConfig };

View File

@@ -0,0 +1,30 @@
export interface IToken {
token: string;
expiresAt: number;
assignedRoles: string[];
}
/**
* an identity is assumed by authentication as a user
* an identity is ephemeral and has to be renewed regularly
*/
export interface IIdentity {
name: string;
userId: string;
type: 'machine' | 'human';
role: 'admin' | 'user' | 'api' | 'cluster';
expiresAt: number;
/** the jwt token should contain above data for verification */
jwt: string;
}
export interface IUser {
id: string;
data: {
type: 'machine' | 'human';
role: 'admin' | 'user' | 'api' | 'cluster';
username?: string;
password?: string;
tokens?: IToken[];
}
}

View File

@@ -0,0 +1,11 @@
export interface IContainerVersionData {
/**
* the docker image url
* example: registry.gitlab.com/hosttoday/ht-docker-node:latest
*/
dockerImageUrl: string;
/**
* the docker image version. Note: This is different from docker tags that are often used for versions.
*/
dockerImageVersion: string;
}

9
ts_interfaces/index.ts Normal file
View File

@@ -0,0 +1,9 @@
import * as data from './data/index.js';
import * as platformservice from './platformservice/index.js';
import * as requests from './requests/index.js';
export {
data,
platformservice,
requests
}

View File

@@ -0,0 +1 @@
The platform folder contains types that can be used for talking with the underlying platform by apps running on serve.zone.

Some files were not shown because too many files have changed in this diff Show More