Compare commits

...

6 Commits

Author SHA1 Message Date
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
29 changed files with 2117 additions and 3253 deletions

62
changelog.md Normal file
View File

@ -0,0 +1,62 @@
# Changelog
## 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

View File

@ -16,26 +16,37 @@
"githost": "gitlab.com", "githost": "gitlab.com",
"gitscope": "servezone/private", "gitscope": "servezone/private",
"gitrepo": "cloudly", "gitrepo": "cloudly",
"description": "A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.", "description": "A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and provide robust configuration and API integration.",
"npmPackagename": "@serve.zone/cloudly", "npmPackagename": "@serve.zone/cloudly",
"license": "UNLICENSED", "license": "UNLICENSED",
"keywords": [ "keywords": [
"cloud management", "cloud management",
"multi-cloud management",
"Docker Swarmkit", "Docker Swarmkit",
"multi-cloud", "container orchestration",
"cloud services",
"DigitalOcean", "DigitalOcean",
"Hetzner Cloud", "Hetzner Cloud",
"Cloudflare", "Cloudflare",
"container orchestration", "configuration management",
"SSL management",
"API integration",
"TypeScript", "TypeScript",
"node.js", "node.js",
"infrastructure automation", "infrastructure automation",
"Cloudron",
"configuration management",
"SSL management",
"APIs",
"devOps", "devOps",
"cloud integration" "cloud API client",
"system logging",
"secret management",
"CI/CD integration",
"task scheduling",
"frontend",
"backend",
"CLI",
"web interface",
"cloud providers",
"security",
"logging"
] ]
} }
}, },

View File

@ -1,8 +1,8 @@
{ {
"name": "@serve.zone/cloudly", "name": "@serve.zone/cloudly",
"version": "1.1.4", "version": "1.1.7",
"private": false, "private": false,
"description": "A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.", "description": "A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and provide robust configuration and API integration.",
"type": "module", "type": "module",
"exports": { "exports": {
".": "./dist/index.js", ".": "./dist/index.js",
@ -21,43 +21,45 @@
"localPublish": "gitzone commit" "localPublish": "gitzone commit"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.1.80", "@git.zone/tsbuild": "^2.1.84",
"@git.zone/tsbundle": "^2.0.15", "@git.zone/tsbundle": "^2.0.15",
"@git.zone/tstest": "^1.0.90", "@git.zone/tstest": "^1.0.90",
"@git.zone/tswatch": "^2.0.23", "@git.zone/tswatch": "^2.0.23",
"@push.rocks/tapbundle": "^5.0.23", "@push.rocks/tapbundle": "^5.0.24",
"@types/node": "^20.14.2" "@types/node": "^22.5.0"
}, },
"dependencies": { "dependencies": {
"@api.global/typedrequest": "3.0.30", "@api.global/typedrequest": "3.0.30",
"@api.global/typedserver": "^3.0.50", "@api.global/typedserver": "^3.0.50",
"@api.global/typedsocket": "^3.0.1", "@api.global/typedsocket": "^3.0.1",
"@apiclient.xyz/cloudflare": "^6.0.1", "@apiclient.xyz/cloudflare": "^6.0.1",
"@apiclient.xyz/docker": "^1.2.2", "@apiclient.xyz/docker": "^1.2.3",
"@apiclient.xyz/hetznercloud": "^1.0.18", "@apiclient.xyz/hetznercloud": "^1.2.0",
"@apiclient.xyz/slack": "^3.0.9", "@apiclient.xyz/slack": "^3.0.9",
"@design.estate/dees-catalog": "^1.0.289", "@design.estate/dees-catalog": "^1.1.6",
"@design.estate/dees-domtools": "^2.0.57", "@design.estate/dees-domtools": "^2.0.57",
"@design.estate/dees-element": "^2.0.34", "@design.estate/dees-element": "^2.0.36",
"@git.zone/tsrun": "^1.2.37", "@git.zone/tsrun": "^1.2.49",
"@push.rocks/early": "^4.0.3", "@push.rocks/early": "^4.0.3",
"@push.rocks/npmextra": "^5.0.17", "@push.rocks/npmextra": "^5.0.23",
"@push.rocks/projectinfo": "^5.0.1", "@push.rocks/projectinfo": "^5.0.1",
"@push.rocks/qenv": "^6.0.5", "@push.rocks/qenv": "^6.0.5",
"@push.rocks/smartacme": "^4.0.8", "@push.rocks/smartacme": "^5.0.0",
"@push.rocks/smartbucket": "^3.0.15", "@push.rocks/smartbucket": "^3.0.22",
"@push.rocks/smartcli": "^4.0.11", "@push.rocks/smartcli": "^4.0.11",
"@push.rocks/smartdata": "^5.2.4", "@push.rocks/smartclickhouse": "^2.0.17",
"@push.rocks/smartdata": "^5.2.6",
"@push.rocks/smartdelay": "^3.0.5", "@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartexit": "^1.0.23", "@push.rocks/smartexit": "^1.0.23",
"@push.rocks/smartfile": "^11.0.20", "@push.rocks/smartexpect": "^1.2.1",
"@push.rocks/smartfile": "^11.0.21",
"@push.rocks/smartguard": "^3.0.2", "@push.rocks/smartguard": "^3.0.2",
"@push.rocks/smartjson": "^5.0.19", "@push.rocks/smartjson": "^5.0.19",
"@push.rocks/smartjwt": "^2.0.4", "@push.rocks/smartjwt": "^2.0.4",
"@push.rocks/smartlog": "^3.0.7", "@push.rocks/smartlog": "^3.0.7",
"@push.rocks/smartlog-destination-clickhouse": "^1.0.11", "@push.rocks/smartlog-destination-clickhouse": "^1.0.13",
"@push.rocks/smartpath": "^5.0.18", "@push.rocks/smartpath": "^5.0.18",
"@push.rocks/smartpromise": "^4.0.3", "@push.rocks/smartpromise": "^4.0.4",
"@push.rocks/smartrequest": "^2.0.22", "@push.rocks/smartrequest": "^2.0.22",
"@push.rocks/smartrx": "^3.0.7", "@push.rocks/smartrx": "^3.0.7",
"@push.rocks/smartssh": "^2.0.1", "@push.rocks/smartssh": "^2.0.1",
@ -67,8 +69,8 @@
"@push.rocks/smartunique": "^3.0.9", "@push.rocks/smartunique": "^3.0.9",
"@push.rocks/taskbuffer": "^3.0.2", "@push.rocks/taskbuffer": "^3.0.2",
"@push.rocks/webjwt": "^1.0.9", "@push.rocks/webjwt": "^1.0.9",
"@serve.zone/interfaces": "^1.0.72", "@serve.zone/interfaces": "^1.0.78",
"@tsclass/tsclass": "^4.0.55" "@tsclass/tsclass": "^4.1.2"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",
@ -95,20 +97,31 @@
"homepage": "https://gitlab.com/servezone/private/cloudly#readme", "homepage": "https://gitlab.com/servezone/private/cloudly#readme",
"keywords": [ "keywords": [
"cloud management", "cloud management",
"multi-cloud management",
"Docker Swarmkit", "Docker Swarmkit",
"multi-cloud", "container orchestration",
"cloud services",
"DigitalOcean", "DigitalOcean",
"Hetzner Cloud", "Hetzner Cloud",
"Cloudflare", "Cloudflare",
"container orchestration", "configuration management",
"SSL management",
"API integration",
"TypeScript", "TypeScript",
"node.js", "node.js",
"infrastructure automation", "infrastructure automation",
"Cloudron",
"configuration management",
"SSL management",
"APIs",
"devOps", "devOps",
"cloud integration" "cloud API client",
"system logging",
"secret management",
"CI/CD integration",
"task scheduling",
"frontend",
"backend",
"CLI",
"web interface",
"cloud providers",
"security",
"logging"
] ]
} }

4778
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,21 +1,28 @@
# @serve.zone/cloudly # @serve.zone/cloudly
A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.
A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and integrate robust configuration and API management capabilities.
## Install ## Install
To install `@serve.zone/cloudly`, run the following command in your terminal: To install `@serve.zone/cloudly`, run the following command in your terminal:
```bash ```bash
npm install @serve.zone/cloudly --save npm install @serve.zone/cloudly --save
``` ```
This will install the package and add it to your project's `package.json` dependencies. This will install the package and add it to your project's `package.json` dependencies.
## Usage ## 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. `@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 ### 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. 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 #### Initializing Cloudly
First, import `Cloudly` class from the package and initialize it as shown below:
First, import the `Cloudly` class from the package and initialize it as shown below:
```typescript ```typescript
import { Cloudly } from '@serve.zone/cloudly'; import { Cloudly } from '@serve.zone/cloudly';
@ -26,6 +33,7 @@ const myCloudlyInstance = new Cloudly();
The `Cloudly` class is the entry point to using the library features. It prepares the environment for configuring the cloud services. The `Cloudly` class is the entry point to using the library features. It prepares the environment for configuring the cloud services.
#### Configuration #### 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. Configuration plays a pivotal role in how `@serve.zone/cloudly` operates. The library expects certain configurations to be provided, which can include credentials for cloud services, database connections, etc.
For example, to configure a connection to MongoDB, specify your MongoDB details as shown: For example, to configure a connection to MongoDB, specify your MongoDB details as shown:
@ -38,16 +46,22 @@ const myCloudlyConfig = {
mongoDbUser: 'myUser', mongoDbUser: 'myUser',
mongoDbPass: 'myPassword', mongoDbPass: 'myPassword',
}, },
// Additional configuration values... cfToken: 'your_cloudflare_api_token',
environment: 'development',
letsEncryptEmail: 'lets_encrypt_email@example.com',
publicUrl: 'example.com',
publicPort: 8443,
hetznerToken: 'your_hetzner_api_token',
}; };
const myCloudlyInstance = new Cloudly(myCloudlyConfig); const myCloudlyInstance = new Cloudly(myCloudlyConfig);
``` ```
#### Managing Docker Swarmkit Cluster ### Managing Docker Swarmkit Clusters
Cloudly allows managing Docker Swarmkit clusters through an abstracted interface, simplifying operations such as deployment and scaling. Below are examples to demonstrate these capabilities. Cloudly allows managing Docker Swarmkit clusters through an abstracted interface, simplifying operations such as deployment and scaling. Below are examples to demonstrate these capabilities.
### Example: Start a Cloudly Instance and Add a Cluster #### Example: Initializing a Cloudly Instance and Adding a Cluster
```typescript ```typescript
import { Cloudly, ClusterManager } from '@serve.zone/cloudly'; import { Cloudly, ClusterManager } from '@serve.zone/cloudly';
@ -65,7 +79,7 @@ async function main() {
letsEncryptEmail: 'lets_encrypt_email@example.com', letsEncryptEmail: 'lets_encrypt_email@example.com',
publicUrl: 'example.com', publicUrl: 'example.com',
publicPort: 8443, publicPort: 8443,
hetznerToken: 'your_hetzner_api_token' hetznerToken: 'your_hetzner_api_token',
}; };
const myCloudlyInstance = new Cloudly(myCloudlyConfig); const myCloudlyInstance = new Cloudly(myCloudlyConfig);
@ -88,10 +102,15 @@ async function main() {
console.log('Cluster added:', newCluster); console.log('Cluster added:', newCluster);
} }
main(); main();
``` ```
### Example: Manage Cloudflare DNS Records ### Additional Use Cases
#### Managing Cloudflare DNS Records
You can manage Cloudflare DNS records using the `CloudflareConnector` provided by Cloudly.
```typescript ```typescript
import { Cloudly, CloudflareConnector } from '@serve.zone/cloudly'; import { Cloudly, CloudflareConnector } from '@serve.zone/cloudly';
@ -109,7 +128,7 @@ async function manageDNSRecords() {
letsEncryptEmail: 'lets_encrypt_email@example.com', letsEncryptEmail: 'lets_encrypt_email@example.com',
publicUrl: 'example.com', publicUrl: 'example.com',
publicPort: 8443, publicPort: 8443,
hetznerToken: 'your_hetzner_api_token' hetznerToken: 'your_hetzner_api_token',
}; };
const myCloudlyInstance = new Cloudly(myCloudlyConfig); const myCloudlyInstance = new Cloudly(myCloudlyConfig);
@ -135,7 +154,9 @@ async function manageDNSRecords() {
manageDNSRecords(); manageDNSRecords();
``` ```
### Example: Integrate with DigitalOcean #### Integrating with DigitalOcean
Integrate with DigitalOcean to manage droplets and other resources.
```typescript ```typescript
import { Cloudly, DigitalOceanConnector } from '@serve.zone/cloudly'; import { Cloudly, DigitalOceanConnector } from '@serve.zone/cloudly';
@ -153,7 +174,7 @@ async function manageDroplet() {
letsEncryptEmail: 'lets_encrypt_email@example.com', letsEncryptEmail: 'lets_encrypt_email@example.com',
publicUrl: 'example.com', publicUrl: 'example.com',
publicPort: 8443, publicPort: 8443,
hetznerToken: 'your_hetzner_api_token' hetznerToken: 'your_hetzner_api_token',
}; };
const myCloudlyInstance = new Cloudly(myCloudlyConfig); const myCloudlyInstance = new Cloudly(myCloudlyConfig);
@ -180,8 +201,11 @@ manageDroplet();
``` ```
### Using Cloudly Web Interface ### Using Cloudly Web Interface
If your project includes a web interface to manage various sections like DNS, deployments, clusters, etc., you can use the provided elements and state management. Below is an example of setting up a dashboard using the components defined: If your project includes a web interface to manage various sections like DNS, deployments, clusters, etc., you can use the provided elements and state management. Below is an example of setting up a dashboard using the components defined:
#### Web Dashboard Example
```typescript ```typescript
import { commitinfo } from '../00_commitinfo_data.js'; import { commitinfo } from '../00_commitinfo_data.js';
import * as plugins from '../plugins.js'; import * as plugins from '../plugins.js';
@ -212,7 +236,7 @@ import { CloudlyViewServices } from './cloudly-view-services.js';
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
'cvault-dashboard': CloudlyDashboard; 'cloudly-dashboard': CloudlyDashboard;
} }
} }
@ -227,6 +251,7 @@ export class CloudlyDashboard extends DeesElement {
constructor() { constructor() {
super(); super();
document.title = `cloudly v${commitinfo.version}`;
const subcription = appstate.dataState const subcription = appstate.dataState
.select((stateArg) => stateArg) .select((stateArg) => stateArg)
.subscribe((dataArg) => { .subscribe((dataArg) => {
@ -309,6 +334,10 @@ export class CloudlyDashboard extends DeesElement {
name: 'Backups', name: 'Backups',
element: CloudlyViewBackups, element: CloudlyViewBackups,
}, },
{
name: 'Fleet',
element: CloudlyViewBackups,
}
] as plugins.deesCatalog.IView[]} ] as plugins.deesCatalog.IView[]}
></dees-simple-appdash> ></dees-simple-appdash>
</dees-simple-login> </dees-simple-login>
@ -329,7 +358,7 @@ export class CloudlyDashboard extends DeesElement {
action: async () => { action: async () => {
await plugins.deesCatalog.DeesModal.createAndShow({ await plugins.deesCatalog.DeesModal.createAndShow({
heading: 'About', heading: 'About',
content: html`configvault ${commitinfo.version}`, content: html`cloudly ${commitinfo.version}`,
menuOptions: [ menuOptions: [
{ {
name: 'close', name: 'close',
@ -352,11 +381,12 @@ export class CloudlyDashboard extends DeesElement {
if (loginState.jwt) { if (loginState.jwt) {
this.jwt = loginState.jwt; this.jwt = loginState.jwt;
await simpleLogin.switchToSlottedContent(); await simpleLogin.switchToSlottedContent();
await appstate.dataState.dispatchAction(appstate.getDataAction, null); await appstate.dataState.dispatchAction(appstate.getAllDataAction, null);
} }
} }
private async login(username: string, password: string) { private async login(username: string, password: string) {
const domtools = await this.domtoolsPromise;
console.log(`attempting to login...`); console.log(`attempting to login...`);
const simpleLogin = this.shadowRoot.querySelector('dees-simple-login'); const simpleLogin = this.shadowRoot.querySelector('dees-simple-login');
const form = simpleLogin.shadowRoot.querySelector('dees-form'); const form = simpleLogin.shadowRoot.querySelector('dees-form');
@ -370,7 +400,7 @@ export class CloudlyDashboard extends DeesElement {
this.jwt = state.jwt; this.jwt = state.jwt;
form.setStatus('success', 'Logged in!'); form.setStatus('success', 'Logged in!');
await simpleLogin.switchToSlottedContent(); await simpleLogin.switchToSlottedContent();
await appstate.dataState.dispatchAction(appstate.getDataAction, null); await appstate.dataState.dispatchAction(appstate.getAllDataAction, null);
} else { } else {
form.setStatus('error', 'Login failed!'); form.setStatus('error', 'Login failed!');
await domtools.convenience.smartdelay.delayFor(2000); await domtools.convenience.smartdelay.delayFor(2000);
@ -382,8 +412,6 @@ export class CloudlyDashboard extends DeesElement {
} }
``` ```
This script sets up a cloud management dashboard for interacting with various cloud services seamlessly. It covers creating clusters, managing DNS records, handling cloud-provider-specific resources, and much more.
With the examples provided above, you should now have a good understanding of how to use `@serve.zone/cloudly` to manage your cloud infrastructure programmatically. For deeper insights and additional features, refer to the documentation relevant to specific modules and methods used in your application. With the examples provided above, you should now have a good understanding of how to use `@serve.zone/cloudly` to manage your cloud infrastructure programmatically. For deeper insights and additional features, refer to the documentation relevant to specific modules and methods used in your application.
## License and Legal Information ## License and Legal Information

View File

@ -0,0 +1,23 @@
import { Qenv } from '@push.rocks/qenv';
const testQenv = new Qenv('./', './.nogit/');
import * as cloudly from '../../ts/index.js';
export const createCloudly = async () => {
const cloudlyConfig: cloudly.ICloudlyConfig = {
cfToken: await testQenv.getEnvVarOnDemand('CF_TOKEN'),
environment: 'integration',
letsEncryptEmail: await testQenv.getEnvVarOnDemand('LETSENCRYPT_EMAIL'),
publicUrl: await testQenv.getEnvVarOnDemand('SERVEZONE_URL'),
publicPort: await testQenv.getEnvVarOnDemand('SERVEZONE_PORT'),
mongoDescriptor: {
mongoDbName: await testQenv.getEnvVarOnDemand('MONGODB_DATABASE'),
mongoDbUser: await testQenv.getEnvVarOnDemand('MONGODB_USER'),
mongoDbPass: await testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'),
mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGODB_URL'),
},
};
const cloudlyInstance = new cloudly.Cloudly();
return cloudlyInstance;
}

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

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

View File

@ -9,15 +9,21 @@ tap.test('should create a new cloudlyApiClient', async () => {
registerAs: 'api', registerAs: 'api',
cloudlyUrl: 'http://localhost:3000', cloudlyUrl: 'http://localhost:3000',
}); });
await testClient.start(); // await testClient.start();
expect(testClient).toBeTruthy(); expect(testClient).toBeTruthy();
}); });
tap.test('should get an identity', async () => {
const identity = await testClient.getIdentityByJumpCode('test');
expect(identity).toBeTruthy();
});
tap.test('should trigger a server action', async () => { tap.test('should trigger a server action', async () => {
}) })
tap.test('should stop the apiclient', async () => { tap.test('should stop the apiclient', async (toolsArg) => {
await toolsArg.delayFor(1000);
await testClient.stop(); await testClient.stop();
}) })

View File

@ -1,28 +1,11 @@
import { expect, tap } from '@push.rocks/tapbundle'; import { expect, tap } from '@push.rocks/tapbundle';
import { Qenv } from '@push.rocks/qenv'; import * as helpers from './helpers/index.js';
const testQenv = new Qenv('./', './.nogit/');
process.env.TESTING_CLOUDLY = 'true';
delete process.env.CLI_CALL;
import * as cloudly from '../ts/index.js'; import * as cloudly from '../ts/index.js';
let testCloudly: cloudly.Cloudly; let testCloudly: cloudly.Cloudly;
tap.test('first test', async () => { tap.test('first test', async () => {
const cloudlyConfig: cloudly.ICloudlyConfig = { testCloudly = await helpers.createCloudly();
cfToken: await testQenv.getEnvVarOnDemand('CF_TOKEN'),
environment: 'integration',
letsEncryptEmail: await testQenv.getEnvVarOnDemand('LETSENCRYPT_EMAIL'),
publicUrl: await testQenv.getEnvVarOnDemand('SERVEZONE_URL'),
publicPort: await testQenv.getEnvVarOnDemand('SERVEZONE_PORT'),
mongoDescriptor: {
mongoDbName: await testQenv.getEnvVarOnDemand('MONGODB_DATABASE'),
mongoDbUser: await testQenv.getEnvVarOnDemand('MONGODB_USER'),
mongoDbPass: await testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'),
mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGODB_URL'),
},
};
testCloudly = new cloudly.Cloudly();
expect(testCloudly).toBeInstanceOf(cloudly.Cloudly); expect(testCloudly).toBeInstanceOf(cloudly.Cloudly);
}); });

View File

@ -1,8 +1,8 @@
/** /**
* autocreated commitinfo by @pushrocks/commitinfo * autocreated commitinfo by @push.rocks/commitinfo
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/cloudly', name: '@serve.zone/cloudly',
version: '1.1.4', version: '1.1.7',
description: 'A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.' description: 'A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and provide robust configuration and API integration.'
} }

View File

@ -9,6 +9,7 @@ export const getUsers = async (cloudlyRef: Cloudly) => {
users.push({ users.push({
id: 'envadmin', id: 'envadmin',
data: { data: {
type: 'human',
username: envAdminUser.split(':')[0], username: envAdminUser.split(':')[0],
password: envAdminUser.split(':')[1], password: envAdminUser.split(':')[1],
role: 'admin', role: 'admin',

View File

@ -34,7 +34,7 @@ export class LetsencryptConnector {
}, },
mongoDescriptor: this.cloudlyRef.config.data.mongoDescriptor, mongoDescriptor: this.cloudlyRef.config.data.mongoDescriptor,
}); });
await this.smartacme.init().catch(err => { await this.smartacme.start().catch(err => {
console.error('error in init', err); console.error('error in init', err);
console.log(`trying again in a few minutes`) console.log(`trying again in a few minutes`)
}); });

View File

@ -9,6 +9,7 @@ import { User } from './classes.user.js';
export interface IJwtData { export interface IJwtData {
userId: string; userId: string;
status: 'loggedIn' | 'loggedOut'; status: 'loggedIn' | 'loggedOut';
expiresAt: number;
} }
export class CloudlyAuthManager { export class CloudlyAuthManager {
@ -33,12 +34,12 @@ export class CloudlyAuthManager {
await this.smartjwtInstance.init(); await this.smartjwtInstance.init();
const kvStore = await this.cloudlyRef.config.appData.getKvStore(); const kvStore = await this.cloudlyRef.config.appData.getKvStore();
const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = await kvStore.readKey('jwtKeys'); const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = (await kvStore.readKey('jwtKeypair')) as plugins.tsclass.network.IJwtKeypair;
if (!existingJwtKeys) { if (!existingJwtKeys) {
await this.smartjwtInstance.createNewKeyPair(); await this.smartjwtInstance.createNewKeyPair();
const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson(); const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson();
await kvStore.writeKey('jwtKeys', newJwtKeys); await kvStore.writeKey('jwtKeypair', newJwtKeys);
} else { } else {
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys); this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
} }
@ -48,6 +49,7 @@ export class CloudlyAuthManager {
'adminLoginWithUsernameAndPassword', 'adminLoginWithUsernameAndPassword',
async (dataArg) => { async (dataArg) => {
let jwt: string; let jwt: string;
let expiresAtTimestamp: number = Date.now() + 3600 * 1000 * 24 * 7;
const user = await User.findUserByUsernameAndPassword(dataArg.username, dataArg.password); const user = await User.findUserByUsernameAndPassword(dataArg.username, dataArg.password);
if (!user) { if (!user) {
logger.log('warn', 'login failed'); logger.log('warn', 'login failed');
@ -55,11 +57,19 @@ export class CloudlyAuthManager {
jwt = await this.smartjwtInstance.createJWT({ jwt = await this.smartjwtInstance.createJWT({
userId: user.id, userId: user.id,
status: 'loggedIn', status: 'loggedIn',
expiresAt: expiresAtTimestamp,
}); });
logger.log('success', 'login successful'); logger.log('success', 'login successful');
} }
return { return {
identity: {
jwt, jwt,
userId: user.id,
name: user.data.username,
expiresAt: expiresAtTimestamp,
role: user.data.role,
type: user.data.type,
},
}; };
} }
) )
@ -68,14 +78,33 @@ export class CloudlyAuthManager {
public async stop () {} public async stop () {}
public adminJwtGuard = new plugins.smartguard.Guard<{jwt: string}>(async (dataArg) => { public validIdentityGuard = new plugins.smartguard.Guard<{identity: plugins.servezoneInterfaces.data.IIdentity}>(async (dataArg) => {
const jwt = dataArg.jwt; 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.smartexpect.expectAsync(this.validIdentityGuard.exec(dataArg)).toBeTrue();
const jwt = dataArg.identity.jwt;
const jwtData: IJwtData = await this.smartjwtInstance.verifyJWTAndGetData(jwt); const jwtData: IJwtData = await this.smartjwtInstance.verifyJWTAndGetData(jwt);
const user = await this.CUser.getInstance({id: jwtData.userId}); const user = await this.CUser.getInstance({id: jwtData.userId});
const isAdminBool = user.data.role === 'admin'; const isAdminBool = user.data.role === 'admin';
console.log(`user is admin: ${isAdminBool}`); console.log(`user is admin: ${isAdminBool}`);
return isAdminBool; return isAdminBool;
}, { }, {
failedHint: 'user is not admin.' failedHint: 'user is not admin.',
name: 'adminIdentityGuard',
}) })
} }

View File

@ -19,9 +19,5 @@ export class User extends plugins.smartdata.SmartDataDbDoc<
public id: string; public id: string;
@plugins.smartdata.svDb() @plugins.smartdata.svDb()
public data: { public data: plugins.servezoneInterfaces.data.IUser['data'];
role: 'admin' | 'user';
username: string;
password: string;
};
} }

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

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

@ -22,12 +22,14 @@ export class ClusterManager {
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_CreateCluster>( this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_CreateCluster>(
new plugins.typedrequest.TypedHandler('createCluster', async (dataArg) => { new plugins.typedrequest.TypedHandler('createCluster', async (dataArg) => {
const cluster = await this.storeCluster({ // TODO: guards
const cluster = await this.createCluster({
id: plugins.smartunique.uniSimple('cluster'), id: plugins.smartunique.uniSimple('cluster'),
data: { data: {
userId: null,
name: dataArg.clusterName, name: dataArg.clusterName,
jumpCode: plugins.smartunique.uniSimple('cluster'), initialJumpToken: plugins.smartunique.uniSimple('initialJumpToken'),
jumpCodeUsedAt: null, initialJumpTokenUsedAt: null,
acmeInfo: null, acmeInfo: null,
cloudlyUrl: `https://${this.cloudlyRef.config.data.publicUrl}:${this.cloudlyRef.config.data.publicPort}/`, cloudlyUrl: `https://${this.cloudlyRef.config.data.publicUrl}:${this.cloudlyRef.config.data.publicPort}/`,
servers: [], servers: [],
@ -57,7 +59,7 @@ export class ClusterManager {
// delete cluster // delete cluster
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_DeleteCluster>( this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_DeleteCluster>(
new plugins.typedrequest.TypedHandler('deleteCluster', async (reqDataArg, toolsArg) => { new plugins.typedrequest.TypedHandler('deleteCluster', async (reqDataArg, toolsArg) => {
await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], reqDataArg); await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqDataArg);
await this.deleteCluster(reqDataArg.clusterId); await this.deleteCluster(reqDataArg.clusterId);
return { return {
success: true, success: true,
@ -80,25 +82,24 @@ export class ClusterManager {
// TODO: implement getclusterConfigByServerIp // TODO: implement getclusterConfigByServerIp
} }
public async getClusterConfigBy_JumpCode(jumpCodeArg: string) { public async getClusterBy_JumpCode(initialJumpTokenArg: string) {
await this.ready.promise; await this.ready.promise;
return await Cluster.getInstance({ return await Cluster.getInstance({
data: { data: {
jumpCode: jumpCodeArg, initialJumpToken: initialJumpTokenArg,
}, },
}); });
} }
public async getClusterConfigBy_ClusterIdentifier( public async getClusterBy_Identity(
clusterIdentifier: plugins.servezoneInterfaces.data.IClusterIdentifier clusterIdentity: plugins.servezoneInterfaces.data.IIdentity
) { ) {
await this.ready.promise; await this.ready.promise;
return await Cluster.getInstance({ return await Cluster.getInstance({
id: clusterIdentifier.clusterId,
data: { data: {
name: clusterIdentifier.clusterName, userId: clusterIdentity.userId,
}, },
}); });
} }
@ -128,13 +129,20 @@ export class ClusterManager {
* @param configName * @param configName
* @param configObjectArg * @param configObjectArg
*/ */
public async storeCluster(configObjectArg: plugins.servezoneInterfaces.data.ICluster) { public async createCluster(configObjectArg: plugins.servezoneInterfaces.data.ICluster) {
let clusterInstance = await Cluster.getInstance({ id: configObjectArg.id }); // TODO: guards
if (!clusterInstance) { // lets create the cluster user
clusterInstance = await Cluster.fromConfigObject(configObjectArg); const clusterUser = new this.cloudlyRef.authManager.CUser();
} else { clusterUser.id = await this.cloudlyRef.authManager.CUser.getNewId();
Object.assign(clusterInstance, configObjectArg); clusterUser.data = {
role: 'cluster',
type: 'machine',
} }
await clusterUser.save();
Object.assign(configObjectArg, {
userId: clusterUser.id,
});
const clusterInstance = await Cluster.fromConfigObject(configObjectArg);
await clusterInstance.save(); await clusterInstance.save();
return clusterInstance; return clusterInstance;
} }

View File

@ -15,22 +15,37 @@ export class CloudlyCoreflowManager {
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByJumpCode>( this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByJumpCode>(
new plugins.typedrequest.TypedHandler('getIdentityByJumpCode', async (requestData) => { new plugins.typedrequest.TypedHandler('getIdentityByJumpCode', async (requestData) => {
const clusterConfig = const cluster =
await this.cloudlyRef.clusterManager.getClusterConfigBy_JumpCode( await this.cloudlyRef.clusterManager.getClusterBy_JumpCode(
requestData.jumpCode requestData.jumpCode
); );
if (!clusterConfig) { if (!cluster) {
throw new plugins.typedrequest.TypedResponseError('The supplied jumpCode is not valid.'); throw new plugins.typedrequest.TypedResponseError('The supplied jumpCode is not valid. No cluster found.');
} }
const user = await this.cloudlyRef.authManager.CUser.getInstance({
id: cluster.data.userId,
});
if (!user) {
throw new plugins.typedrequest.TypedResponseError('The supplied jumpCode is not valid. No user found.');
}
const expiryTimestamp = Date.now() + 3600 * 1000 * 24 * 365;
return { return {
clusterIdentifier: { identity: {
clusterId: clusterConfig.id, name: cluster.data.name,
clusterName: clusterConfig.data.name, role: 'cluster',
type: 'machine',
userId: cluster.data.userId,
expiresAt: expiryTimestamp,
clusterId: cluster.id,
clusterName: cluster.data.name,
jwt: await this.cloudlyRef.authManager.smartjwtInstance.createJWT({ jwt: await this.cloudlyRef.authManager.smartjwtInstance.createJWT({
status: 'loggedIn', status: 'loggedIn',
userId: 'cluster:' + clusterConfig.id, // TODO: create real users for clusters userId: cluster.data.userId,
expiresAt: expiryTimestamp,
}) })
}, },
}; };
@ -42,16 +57,16 @@ export class CloudlyCoreflowManager {
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>( new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>(
'getClusterConfig', 'getClusterConfig',
async (dataArg) => { async (dataArg) => {
const clusterIdentifier = dataArg.clusterIdentifier; const identity = dataArg.identity;
console.log('trying to get clusterConfigSet'); console.log('trying to get clusterConfigSet');
console.log(dataArg); console.log(dataArg);
const clusterConfigSet = const cluster =
await this.cloudlyRef.clusterManager.getClusterConfigBy_ClusterIdentifier( await this.cloudlyRef.clusterManager.getClusterBy_Identity(
clusterIdentifier identity
); );
console.log('got cluster config and sending it back to coreflow'); console.log('got cluster config and sending it back to coreflow');
return { return {
configData: await clusterConfigSet.createSavableObject(), configData: await cluster.createSavableObject(),
deploymentDirectives: [], deploymentDirectives: [],
}; };
} }
@ -60,14 +75,14 @@ export class CloudlyCoreflowManager {
// lets enable getting of certificates // lets enable getting of certificates
this.typedRouter.addTypedHandler( this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetSslCertificate>( new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain>(
'getSslCertificate', 'getCertificateForDomain',
async (dataArg) => { 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( 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 { return {
certificate: await cert.createSavableObject(), certificate: await cert.createSavableObject(),
}; };

View File

@ -26,7 +26,7 @@ export class ImageManager {
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>( new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
'createImage', 'createImage',
async (reqArg, toolsArg) => { async (reqArg, toolsArg) => {
await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], reqArg); await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
const image = await this.CImage.create({ const image = await this.CImage.create({
name: reqArg.name, name: reqArg.name,
description: reqArg.description, description: reqArg.description,
@ -43,7 +43,7 @@ export class ImageManager {
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_DeleteImage>( new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_DeleteImage>(
'deleteImage', 'deleteImage',
async (reqArg, toolsArg) => { async (reqArg, toolsArg) => {
await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], reqArg); await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
const image = await this.CImage.getInstance({ const image = await this.CImage.getInstance({
id: reqArg.imageId, id: reqArg.imageId,
}); });
@ -57,7 +57,7 @@ export class ImageManager {
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>( new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
'getAllImages', 'getAllImages',
async (requestArg, toolsArg) => { async (requestArg, toolsArg) => {
await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], requestArg); await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], requestArg);
const images = await this.CImage.getInstances({}); const images = await this.CImage.getInstances({});
return { return {
images: await Promise.all( images: await Promise.all(

View File

@ -39,8 +39,8 @@ export class CloudlySecretManager {
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>( new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
'adminGetConfigBundlesAndSecretGroups', 'adminGetConfigBundlesAndSecretGroups',
async (dataArg, toolsArg) => { async (dataArg, toolsArg) => {
await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], dataArg); await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], dataArg);
dataArg.jwt dataArg.identity.jwt
const secretBundles = await SecretBundle.getInstances({}); const secretBundles = await SecretBundle.getInstances({});
const secretGroups = await SecretGroup.getInstances({}); const secretGroups = await SecretGroup.getInstances({});
return { return {

View File

@ -29,11 +29,14 @@ apt-get install -y nodejs zsh
zsh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended zsh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
npm config set unsafe-perm true 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 # lets make sure we use the correct npm registry
bash -c "npm config set registry ${this.optionsArg.npmRegistry}" bash -c "npm config set registry ${this.optionsArg.npmRegistry}"
# lets install spark # lets install spark
bash -c "npm install -g @serve.zone/spark" bash -c "pnpm install -g @serve.zone/spark"
# lets install the spark daemon # lets install the spark daemon
bash -c "spark installdaemon" bash -c "spark installdaemon"

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.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> { export class Service extends plugins.smartdata.SmartDataDbDoc<Service, plugins.servezoneInterfaces.data.IService, ServiceManager> {

View File

@ -29,9 +29,11 @@ import * as qenv from '@push.rocks/qenv';
import * as smartacme from '@push.rocks/smartacme'; import * as smartacme from '@push.rocks/smartacme';
import * as smartbucket from '@push.rocks/smartbucket'; import * as smartbucket from '@push.rocks/smartbucket';
import * as smartcli from '@push.rocks/smartcli'; import * as smartcli from '@push.rocks/smartcli';
import * as smartclickhouse from '@push.rocks/smartclickhouse';
import * as smartdata from '@push.rocks/smartdata'; import * as smartdata from '@push.rocks/smartdata';
import * as smartdelay from '@push.rocks/smartdelay'; import * as smartdelay from '@push.rocks/smartdelay';
import * as smartexit from '@push.rocks/smartexit'; import * as smartexit from '@push.rocks/smartexit';
import * as smartexpect from '@push.rocks/smartexpect';
import * as smartfile from '@push.rocks/smartfile'; import * as smartfile from '@push.rocks/smartfile';
import * as smartguard from '@push.rocks/smartguard'; import * as smartguard from '@push.rocks/smartguard';
import * as smartjson from '@push.rocks/smartjson'; import * as smartjson from '@push.rocks/smartjson';
@ -53,8 +55,10 @@ export {
smartacme, smartacme,
smartbucket, smartbucket,
smartcli, smartcli,
smartclickhouse,
smartdata, smartdata,
smartexit, smartexit,
smartexpect,
smartdelay, smartdelay,
smartfile, smartfile,
smartguard, smartguard,

View File

@ -63,12 +63,19 @@ export class CloudlyApiClient {
await this.typedsocketClient.stop(); await this.typedsocketClient.stop();
} }
public identity: plugins.servezoneInterfaces.data.IClusterIdentifier; public identity: plugins.servezoneInterfaces.data.IIdentity;
public async getIdentityByJumpCode( public async getIdentityByJumpCode(
jumpCodeArg: string, jumpCodeArg: string,
tagConnection = false, optionsArg?: {
statefullIdentity = true tagConnection?: boolean;
): Promise<plugins.servezoneInterfaces.data.IClusterIdentifier> { statefullIdentity?: boolean;
}
): Promise<plugins.servezoneInterfaces.data.IIdentity> {
optionsArg = Object.assign({}, {
tagConnection: false,
statefullIdentity: true,
}, optionsArg);
const identityRequest = const identityRequest =
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByJumpCode>( this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByJumpCode>(
'getIdentityByJumpCode' 'getIdentityByJumpCode'
@ -78,42 +85,47 @@ export class CloudlyApiClient {
jumpCode: jumpCodeArg, jumpCode: jumpCodeArg,
}); });
console.log('got identity response'); console.log('got identity response');
const identity = response.clusterIdentifier; const identity = response.identity;
if (tagConnection) { if (optionsArg.tagConnection) {
this.typedsocketClient.addTag('identity', identity); this.typedsocketClient.addTag('identity', identity);
} }
if (statefullIdentity) { if (optionsArg.statefullIdentity) {
this.identity = identity; this.identity = identity;
} }
return identity; return identity;
} }
/**
* will use statefull identity by default
*/
public async getClusterConfigFromCloudlyByIdentity( public async getClusterConfigFromCloudlyByIdentity(
identityArg: plugins.servezoneInterfaces.data.IClusterIdentifier identityArg: plugins.servezoneInterfaces.data.IIdentity = this.identity
): Promise<plugins.servezoneInterfaces.data.ICluster> { ): Promise<plugins.servezoneInterfaces.data.ICluster> {
const clusterConfigRequest = const clusterConfigRequest =
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>( this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>(
'getClusterConfig' 'getClusterConfig'
); );
const response = await clusterConfigRequest.fire({ const response = await clusterConfigRequest.fire({
jwt: '', identity: identityArg,
clusterIdentifier: identityArg,
}); });
return response.configData; return response.configData;
} }
/**
* will use statefull identity by default
*/
public async getServerConfigFromCloudlyByIdentity( public async getServerConfigFromCloudlyByIdentity(
identityArg: plugins.servezoneInterfaces.data.IClusterIdentifier identityArg: plugins.servezoneInterfaces.data.IIdentity = this.identity
): Promise<plugins.servezoneInterfaces.data.IServer> { ): Promise<plugins.servezoneInterfaces.data.IServer> {
const serverConfigRequest = const serverConfigRequest =
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetServerConfig>( this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetServerConfig>(
'getServerConfig' 'getServerConfig'
); );
const response = await serverConfigRequest.fire({ const response = await serverConfigRequest.fire({
jwt: '', // TODO: do proper auth here identity: identityArg,
serverId: '', // TODO: get server id here serverId: '', // TODO: get server id here
}); });
return response.configData; return response.configData;
@ -121,19 +133,24 @@ export class CloudlyApiClient {
/** /**
* gets a certificate for a domain used by a service * gets a certificate for a domain used by a service
* @param serviceNameArg
* @param domainNameArg
*/ */
public async getCertificateForDomainOverHttps( public async getCertificateForDomain(optionsArg: {
domainNameArg: string domainName: string;
): Promise<plugins.tsclass.network.ICert> { 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 = const typedCertificateRequest =
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetSslCertificate>( this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain>(
'getSslCertificate' 'getCertificateForDomain'
); );
const typedResponse = await typedCertificateRequest.fire({ const typedResponse = await typedCertificateRequest.fire({
authToken: '', // do proper auth here identity: this.identity, // do proper auth here
requiredCertName: domainNameArg, domainName: optionsArg.domainName,
type: optionsArg.type,
}); });
return typedResponse.certificate; return typedResponse.certificate;
} }

View File

@ -7,7 +7,7 @@ export class Image implements plugins.servezoneInterfaces.data.IImage {
'getAllImages' 'getAllImages'
); );
const response = await getAllImagesTR.fire({ const response = await getAllImagesTR.fire({
jwt: cloudlyClientRef.identity.jwt, identity: cloudlyClientRef.identity,
}); });
const resultImages: Image[] = []; const resultImages: Image[] = [];
for (const image of response.images) { for (const image of response.images) {
@ -18,6 +18,23 @@ export class Image implements plugins.servezoneInterfaces.data.IImage {
return resultImages; return resultImages;
} }
/**
* 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 // INSTANCE
cloudlyClientRef: CloudlyApiClient; cloudlyClientRef: CloudlyApiClient;
@ -36,7 +53,7 @@ export class Image implements plugins.servezoneInterfaces.data.IImage {
'getImageMetadata' 'getImageMetadata'
); );
const response = await getVersionsTR.fire({ const response = await getVersionsTR.fire({
jwt: this.cloudlyClientRef.identity.jwt, identity: this.cloudlyClientRef.identity,
imageId: this.id, imageId: this.id,
}); });
Object.assign(this, response.image); Object.assign(this, response.image);
@ -54,7 +71,7 @@ export class Image implements plugins.servezoneInterfaces.data.IImage {
); );
const virtualStream = new plugins.typedrequest.VirtualStream(); const virtualStream = new plugins.typedrequest.VirtualStream();
const response = await pullImageTR.fire({ const response = await pullImageTR.fire({
jwt: this.cloudlyClientRef.identity.jwt, identity: this.cloudlyClientRef.identity,
imageId: this.id, imageId: this.id,
versionString: '', versionString: '',
imageStream: virtualStream, imageStream: virtualStream,
@ -72,7 +89,7 @@ export class Image implements plugins.servezoneInterfaces.data.IImage {
'pullImageVersion' 'pullImageVersion'
); );
const response = await pullImageTR.fire({ const response = await pullImageTR.fire({
jwt: this.cloudlyClientRef.identity.jwt, identity: this.cloudlyClientRef.identity,
imageId: this.id, imageId: this.id,
versionString: versionStringArg, versionString: versionStringArg,
}); });

View File

@ -1,8 +1,8 @@
/** /**
* autocreated commitinfo by @pushrocks/commitinfo * autocreated commitinfo by @push.rocks/commitinfo
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/cloudly', name: '@serve.zone/cloudly',
version: '1.1.4', version: '1.1.7',
description: 'A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.' description: 'A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and provide robust configuration and API integration.'
} }

View File

@ -3,11 +3,11 @@ import * as domtools from '@design.estate/dees-domtools';
const appstate = new plugins.deesDomtools.plugins.smartstate.Smartstate(); const appstate = new plugins.deesDomtools.plugins.smartstate.Smartstate();
export interface ILoginState { export interface ILoginState {
jwt: string; identity: plugins.interfaces.data.IIdentity;
} }
export const loginStatePart: plugins.smartstate.StatePart<unknown, ILoginState> = await appstate.getStatePart<ILoginState>( export const loginStatePart: plugins.smartstate.StatePart<unknown, ILoginState> = await appstate.getStatePart<ILoginState>(
'login', 'login',
{ jwt: null }, { identity: null },
'persistent' 'persistent'
); );
@ -25,7 +25,7 @@ export const loginAction = loginStatePart.createAction<{ username: string; passw
}); });
return { return {
...currentState, ...currentState,
...(response.jwt ? { jwt: response.jwt } : {}), ...(response.identity ? { identity: response.identity } : {}),
}; };
} }
); );
@ -34,7 +34,7 @@ export const logoutAction = loginStatePart.createAction(async (statePartArg) =>
const currentState = statePartArg.getState(); const currentState = statePartArg.getState();
return { return {
...currentState, ...currentState,
jwt: null, identity: null,
}; };
}); });
@ -81,7 +81,7 @@ export const getAllDataAction = dataState.createAction(async (statePartArg, part
'adminGetConfigBundlesAndSecretGroups' 'adminGetConfigBundlesAndSecretGroups'
); );
const response = await trGetSecrets.fire({ const response = await trGetSecrets.fire({
jwt: loginStatePart.getState().jwt, identity: loginStatePart.getState().identity,
}); });
currentState = { currentState = {
...currentState, ...currentState,
@ -95,7 +95,7 @@ export const getAllDataAction = dataState.createAction(async (statePartArg, part
'getAllImages' 'getAllImages'
); );
const responseImages = await trGetImages.fire({ const responseImages = await trGetImages.fire({
jwt: loginStatePart.getState().jwt, identity: loginStatePart.getState().identity,
}); });
currentState = { currentState = {
...currentState, ...currentState,
@ -109,7 +109,7 @@ export const getAllDataAction = dataState.createAction(async (statePartArg, part
'getAllClusters' 'getAllClusters'
); );
const responseClusters = await trGetClusters.fire({ const responseClusters = await trGetClusters.fire({
jwt: loginStatePart.getState().jwt, identity: loginStatePart.getState().identity,
}); });
currentState = { currentState = {
@ -130,7 +130,7 @@ export const createSecretGroupAction = dataState.createAction(
'adminCreateConfigBundlesAndSecretGroups' 'adminCreateConfigBundlesAndSecretGroups'
); );
const response = await trCreateSecretGroup.fire({ const response = await trCreateSecretGroup.fire({
jwt: loginStatePart.getState().jwt, identity: loginStatePart.getState().identity,
secretBundles: [], secretBundles: [],
secretGroups: [payloadArg], secretGroups: [payloadArg],
}); });
@ -149,7 +149,7 @@ export const deleteSecretGroupAction = dataState.createAction(
'adminDeleteConfigBundlesAndSecretGroups' 'adminDeleteConfigBundlesAndSecretGroups'
); );
const response = await trDeleteSecretGroup.fire({ const response = await trDeleteSecretGroup.fire({
jwt: loginStatePart.getState().jwt, identity: loginStatePart.getState().identity,
secretBundleIds: [], secretBundleIds: [],
secretGroupIds: [payloadArg.secretGroupId], secretGroupIds: [payloadArg.secretGroupId],
}); });
@ -168,7 +168,7 @@ export const deleteSecretBundleAction = dataState.createAction(
'adminDeleteConfigBundlesAndSecretGroups' 'adminDeleteConfigBundlesAndSecretGroups'
); );
const response = await trDeleteConfigBundle.fire({ const response = await trDeleteConfigBundle.fire({
jwt: loginStatePart.getState().jwt, identity: loginStatePart.getState().identity,
secretBundleIds: [payloadArg.configBundleId], secretBundleIds: [payloadArg.configBundleId],
secretGroupIds: [], secretGroupIds: [],
}); });
@ -187,7 +187,7 @@ export const createImageAction = dataState.createAction(
'createImage' 'createImage'
); );
const response = await trCreateImage.fire({ const response = await trCreateImage.fire({
jwt: loginStatePart.getState().jwt, identity: loginStatePart.getState().identity,
name: payloadArg.imageName, name: payloadArg.imageName,
description: payloadArg.description, description: payloadArg.description,
}); });
@ -210,7 +210,7 @@ export const deleteImageAction = dataState.createAction(
'deleteImage' 'deleteImage'
); );
const response = await trDeleteImage.fire({ const response = await trDeleteImage.fire({
jwt: loginStatePart.getState().jwt, identity: loginStatePart.getState().identity,
imageId: payloadArg.imageId, imageId: payloadArg.imageId,
}); });
currentState = { currentState = {
@ -238,7 +238,7 @@ export const addClusterAction = dataState.createAction(
'createCluster' 'createCluster'
); );
const response = await trAddCluster.fire({ const response = await trAddCluster.fire({
jwt: loginStatePart.getState().jwt, identity: loginStatePart.getState().identity,
...payloadArg, ...payloadArg,
}); });
currentState = { currentState = {

View File

@ -33,7 +33,7 @@ declare global {
@customElement('cloudly-dashboard') @customElement('cloudly-dashboard')
export class CloudlyDashboard extends DeesElement { export class CloudlyDashboard extends DeesElement {
@state() private jwt: string; @state() private identity: plugins.interfaces.data.IIdentity;
@state() private data: appstate.IDataState = { @state() private data: appstate.IDataState = {
secretGroups: [], secretGroups: [],
secretBundles: [], secretBundles: [],
@ -169,8 +169,8 @@ export class CloudlyDashboard extends DeesElement {
const domtools = await this.domtoolsPromise; const domtools = await this.domtoolsPromise;
const loginState = appstate.loginStatePart.getState(); const loginState = appstate.loginStatePart.getState();
console.log(loginState); console.log(loginState);
if (loginState.jwt) { if (loginState.identity) {
this.jwt = loginState.jwt; this.identity = loginState.identity;
await simpleLogin.switchToSlottedContent(); await simpleLogin.switchToSlottedContent();
await appstate.dataState.dispatchAction(appstate.getAllDataAction, null); await appstate.dataState.dispatchAction(appstate.getAllDataAction, null);
} }
@ -186,9 +186,9 @@ export class CloudlyDashboard extends DeesElement {
username, username,
password, password,
}); });
if (state.jwt) { if (state.identity) {
console.log('got jwt'); console.log('got jwt');
this.jwt = state.jwt; this.identity = state.identity;
form.setStatus('success', 'Logged in!'); form.setStatus('success', 'Logged in!');
await simpleLogin.switchToSlottedContent(); await simpleLogin.switchToSlottedContent();
await appstate.dataState.dispatchAction(appstate.getAllDataAction, null); await appstate.dataState.dispatchAction(appstate.getAllDataAction, null);