Compare commits

..

19 Commits

Author SHA1 Message Date
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
67 changed files with 4398 additions and 2953 deletions

84
changelog.md Normal file
View File

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

View File

@ -16,26 +16,37 @@
"githost": "gitlab.com",
"gitscope": "servezone/private",
"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",
"license": "UNLICENSED",
"keywords": [
"cloud management",
"multi-cloud management",
"Docker Swarmkit",
"multi-cloud",
"container orchestration",
"cloud services",
"DigitalOcean",
"Hetzner Cloud",
"Cloudflare",
"container orchestration",
"configuration management",
"SSL management",
"API integration",
"TypeScript",
"node.js",
"infrastructure automation",
"Cloudron",
"configuration management",
"SSL management",
"APIs",
"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",
"version": "1.1.1",
"version": "1.2.0",
"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",
"exports": {
".": "./dist/index.js",
@ -18,55 +18,60 @@
"start": "node cli.js",
"startTs": "node cli.ts.js",
"watch": "tswatch website",
"localPublish": "gitzone commit"
"publish": "tspublish"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.1.80",
"@git.zone/tsbuild": "^2.1.84",
"@git.zone/tsbundle": "^2.0.15",
"@git.zone/tspublish": "^1.3.0",
"@git.zone/tstest": "^1.0.90",
"@git.zone/tswatch": "^2.0.23",
"@push.rocks/tapbundle": "^5.0.23",
"@types/node": "^20.12.14"
"@push.rocks/tapbundle": "^5.3.0",
"@types/node": "^22.7.7"
},
"dependencies": {
"@api.global/typedrequest": "3.0.29",
"@api.global/typedserver": "^3.0.50",
"@api.global/typedrequest": "3.1.10",
"@api.global/typedserver": "^3.0.51",
"@api.global/typedsocket": "^3.0.1",
"@apiclient.xyz/cloudflare": "^6.0.1",
"@apiclient.xyz/digitalocean": "^1.0.5",
"@apiclient.xyz/hetznercloud": "^1.0.18",
"@apiclient.xyz/docker": "^1.2.7",
"@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.2.0",
"@design.estate/dees-domtools": "^2.0.64",
"@design.estate/dees-element": "^2.0.39",
"@git.zone/tsrun": "^1.2.49",
"@push.rocks/early": "^4.0.3",
"@push.rocks/npmextra": "^5.0.13",
"@push.rocks/npmextra": "^5.0.23",
"@push.rocks/projectinfo": "^5.0.1",
"@push.rocks/qenv": "^6.0.5",
"@push.rocks/smartacme": "^4.0.8",
"@push.rocks/smartbucket": "^3.0.9",
"@push.rocks/smartacme": "^5.0.0",
"@push.rocks/smartbucket": "^3.0.23",
"@push.rocks/smartcli": "^4.0.11",
"@push.rocks/smartdata": "^5.2.4",
"@push.rocks/smartclickhouse": "^2.0.17",
"@push.rocks/smartdata": "^5.2.10",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartexit": "^1.0.23",
"@push.rocks/smartfile": "^11.0.15",
"@push.rocks/smartguard": "^3.0.2",
"@push.rocks/smartexpect": "^1.2.1",
"@push.rocks/smartfile": "^11.0.21",
"@push.rocks/smartguard": "^3.1.0",
"@push.rocks/smartjson": "^5.0.19",
"@push.rocks/smartjwt": "^2.0.4",
"@push.rocks/smartlog": "^3.0.6",
"@push.rocks/smartlog-destination-clickhouse": "^1.0.11",
"@push.rocks/smartjwt": "^2.2.1",
"@push.rocks/smartlog": "^3.0.7",
"@push.rocks/smartlog-destination-clickhouse": "^1.0.13",
"@push.rocks/smartpath": "^5.0.18",
"@push.rocks/smartpromise": "^4.0.3",
"@push.rocks/smartpromise": "^4.0.4",
"@push.rocks/smartrequest": "^2.0.22",
"@push.rocks/smartrx": "^3.0.7",
"@push.rocks/smartssh": "^2.0.1",
"@push.rocks/smartstate": "^2.0.19",
"@push.rocks/smartstream": "^3.2.4",
"@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.61",
"@tsclass/tsclass": "^4.0.54"
"@serve.zone/interfaces": "^1.1.2",
"@tsclass/tsclass": "^4.1.2"
},
"files": [
"ts/**/*",
@ -93,20 +98,31 @@
"homepage": "https://gitlab.com/servezone/private/cloudly#readme",
"keywords": [
"cloud management",
"multi-cloud management",
"Docker Swarmkit",
"multi-cloud",
"container orchestration",
"cloud services",
"DigitalOcean",
"Hetzner Cloud",
"Cloudflare",
"container orchestration",
"configuration management",
"SSL management",
"API integration",
"TypeScript",
"node.js",
"infrastructure automation",
"Cloudron",
"configuration management",
"SSL management",
"APIs",
"devOps",
"cloud integration"
"cloud API client",
"system logging",
"secret management",
"CI/CD integration",
"task scheduling",
"frontend",
"backend",
"CLI",
"web interface",
"cloud providers",
"security",
"logging"
]
}

5945
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
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
To install `@serve.zone/cloudly`, run the following command in your terminal:
```bash
npm install @serve.zone/cloudly --save
```
This will install the package and add it to your project's `package.json` dependencies.
## Usage
`@serve.zone/cloudly` is designed to help you manage and configure cloud environments. This package provides a comprehensive TypeScript and ESM-based interface for interacting with various cloud services, including Docker Swarmkit cluster management, and integration with cloud providers such as DigitalOcean, Hetzner Cloud, and Cloudflare.
### Getting Started
Before diving into the specifics, ensure your environment is properly set up. This includes having Node.js installed (preferably the latest LTS version), and if you are working in a TypeScript project, ensure TypeScript is configured.
#### 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
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.
#### Configuration
Configuration plays a pivotal role in how `@serve.zone/cloudly` operates. The library expects certain configurations to be provided, which can include credentials for cloud services, database connections, etc.
For example, to configure a connection to MongoDB, specify your MongoDB details as shown:
@ -38,16 +46,22 @@ const myCloudlyConfig = {
mongoDbUser: 'myUser',
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);
```
#### 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.
### Example: Start a Cloudly Instance and Add a Cluster
#### Example: Initializing a Cloudly Instance and Adding a Cluster
```typescript
import { Cloudly, ClusterManager } from '@serve.zone/cloudly';
@ -65,7 +79,7 @@ async function main() {
letsEncryptEmail: 'lets_encrypt_email@example.com',
publicUrl: 'example.com',
publicPort: 8443,
hetznerToken: 'your_hetzner_api_token'
hetznerToken: 'your_hetzner_api_token',
};
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
@ -88,10 +102,15 @@ async function main() {
console.log('Cluster added:', newCluster);
}
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
import { Cloudly, CloudflareConnector } from '@serve.zone/cloudly';
@ -109,7 +128,7 @@ async function manageDNSRecords() {
letsEncryptEmail: 'lets_encrypt_email@example.com',
publicUrl: 'example.com',
publicPort: 8443,
hetznerToken: 'your_hetzner_api_token'
hetznerToken: 'your_hetzner_api_token',
};
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
@ -135,7 +154,9 @@ async function manageDNSRecords() {
manageDNSRecords();
```
### Example: Integrate with DigitalOcean
#### Integrating with DigitalOcean
Integrate with DigitalOcean to manage droplets and other resources.
```typescript
import { Cloudly, DigitalOceanConnector } from '@serve.zone/cloudly';
@ -153,7 +174,7 @@ async function manageDroplet() {
letsEncryptEmail: 'lets_encrypt_email@example.com',
publicUrl: 'example.com',
publicPort: 8443,
hetznerToken: 'your_hetzner_api_token'
hetznerToken: 'your_hetzner_api_token',
};
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
@ -180,8 +201,11 @@ manageDroplet();
```
### 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:
#### Web Dashboard Example
```typescript
import { commitinfo } from '../00_commitinfo_data.js';
import * as plugins from '../plugins.js';
@ -212,7 +236,7 @@ import { CloudlyViewServices } from './cloudly-view-services.js';
declare global {
interface HTMLElementTagNameMap {
'cvault-dashboard': CloudlyDashboard;
'cloudly-dashboard': CloudlyDashboard;
}
}
@ -227,6 +251,7 @@ export class CloudlyDashboard extends DeesElement {
constructor() {
super();
document.title = `cloudly v${commitinfo.version}`;
const subcription = appstate.dataState
.select((stateArg) => stateArg)
.subscribe((dataArg) => {
@ -309,6 +334,10 @@ export class CloudlyDashboard extends DeesElement {
name: 'Backups',
element: CloudlyViewBackups,
},
{
name: 'Fleet',
element: CloudlyViewBackups,
}
] as plugins.deesCatalog.IView[]}
></dees-simple-appdash>
</dees-simple-login>
@ -329,7 +358,7 @@ export class CloudlyDashboard extends DeesElement {
action: async () => {
await plugins.deesCatalog.DeesModal.createAndShow({
heading: 'About',
content: html`configvault ${commitinfo.version}`,
content: html`cloudly ${commitinfo.version}`,
menuOptions: [
{
name: 'close',
@ -352,11 +381,12 @@ export class CloudlyDashboard extends DeesElement {
if (loginState.jwt) {
this.jwt = loginState.jwt;
await simpleLogin.switchToSlottedContent();
await appstate.dataState.dispatchAction(appstate.getDataAction, null);
await appstate.dataState.dispatchAction(appstate.getAllDataAction, null);
}
}
private async login(username: string, password: string) {
const domtools = await this.domtoolsPromise;
console.log(`attempting to login...`);
const simpleLogin = this.shadowRoot.querySelector('dees-simple-login');
const form = simpleLogin.shadowRoot.querySelector('dees-form');
@ -370,7 +400,7 @@ export class CloudlyDashboard extends DeesElement {
this.jwt = state.jwt;
form.setStatus('success', 'Logged in!');
await simpleLogin.switchToSlottedContent();
await appstate.dataState.dispatchAction(appstate.getDataAction, null);
await appstate.dataState.dispatchAction(appstate.getAllDataAction, null);
} else {
form.setStatus('error', 'Login failed!');
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.
## License and Legal Information

View File

@ -0,0 +1,27 @@
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;
}
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';

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

@ -0,0 +1,80 @@
import { tap, expect } from '@push.rocks/tapbundle';
import * as helpers from './helpers/index.js';
import * as cloudly from '../ts/index.js';
import * as cloudlyApiClient from '../ts_apiclient/index.js';
import { Image } from '../ts_apiclient/classes.image.js';
let testCloudly: cloudly.Cloudly;
let testClient: cloudlyApiClient.CloudlyApiClient;
tap.preTask('should start cloudly', async () => {
testCloudly = await helpers.createCloudly();
await testCloudly.start();
});
tap.preTask('should create a new machine user for testing', async () => {
const machineUser = new testCloudly.authManager.CUser();
machineUser.id = await testCloudly.authManager.CUser.getNewId();
machineUser.data = {
type: 'machine',
username: 'test',
password: 'test',
tokens: [{
token: 'test',
expiresAt: Date.now() + 3600 * 1000 * 24 * 365,
assignedRoles: ['admin'],
}],
role: 'admin',
};
await machineUser.save();
});
tap.test('should create a new cloudlyApiClient', async () => {
testClient = new cloudlyApiClient.CloudlyApiClient({
registerAs: 'api',
cloudlyUrl: `http://localhost:${await helpers.getEnvVarOnDemand('SERVEZONE_PORT')}`,
});
await testClient.start();
expect(testClient).toBeTruthy();
});
tap.test('create a new machine user', async () => {
const machineUser = await testCloudly.authManager.CUser.createMachineUser('test', 'api');
machineUser.data.tokens.push({
token: 'test',
expiresAt: Date.now() + 3600 * 1000 * 24 * 365,
assignedRoles: ['api'],
})
await machineUser.save();
})
tap.test('should get an identity', async () => {
const identity = await testClient.getIdentityByToken('test');
expect(identity).toBeTruthy();
console.log(identity);
});
let image: Image;
tap.test('should create and upload an image', async () => {
image = await testClient.images.createImage({
name: 'test',
description: 'test'
});
console.log('created image: ', image);
expect(image).toBeInstanceOf(Image);
})
tap.test('should upload an image version', async () => {
const imageStream = await helpers.getAlpineImageReadableStream();
await image.pushImageVersion('v1.0.0', imageStream);
});
tap.test('should stop the apiclient', async (toolsArg) => {
await toolsArg.delayFor(10000);
await 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 * 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);
});

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.1.1',
description: 'A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.'
version: '1.2.0',
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

@ -63,6 +63,8 @@ for (let i = 0; i < demoSecretGroups.length; i++) {
id: `configBundleId${i + 1}`,
data: {
name: `Demo Config Bundle ${i + 1}`,
includedImages: [],
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

@ -43,7 +43,7 @@ export const installDemoData = async (cloudlyRef: Cloudly) => {
}
const demoDataUsers = await import('./demo.data.users.js');
for (const user of demoDataUsers.users) {
for (const user of await demoDataUsers.getUsers(cloudlyRef)) {
const userInstance = new cloudlyRef.authManager.CUser();
Object.assign(userInstance, user);
await userInstance.save();

View File

@ -15,10 +15,10 @@ import { MongodbConnector } from './connector.mongodb/connector.js';
// processes
import { CloudlyCoreflowManager } from './manager.coreflow/coreflowmanager.js';
import { ClusterManager } from './manager.cluster/clustermanager.js';
import { ClusterManager } from './manager.cluster/classes.clustermanager.js';
import { CloudlyTaskmanager } from './manager.task/taskmanager.js';
import { CloudlySecretManager } from './manager.secret/classes.secretmanager.js'
import { CloudlyServerManager } from './manager.server/servermanager.js';
import { CloudlyServerManager } from './manager.server/classes.servermanager.js';
import { ExternalApiManager } from './manager.status/statusmanager.js';
import { ImageManager } from './manager.image/classes.imagemanager.js';
import { logger } from './logger.js';

View File

@ -40,6 +40,7 @@ export class CloudlyConfig {
useSsl: true,
},
sslMode: 'SERVEZONE_SSLMODE' as plugins.servezoneInterfaces.data.ICloudlyConfig['sslMode'],
servezoneAdminaccount: 'SERVEZONE_ADMINACCOUNT',
},
requiredKeys: [
'cfToken',

View File

@ -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
);
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 ? {
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

@ -34,7 +34,7 @@ export class LetsencryptConnector {
},
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.log(`trying again in a few minutes`)
});

View File

@ -1,12 +0,0 @@
import * as plugins from '../plugins.js';
export const users: plugins.servezoneInterfaces.data.IUser[] = [
{
id: 'user1',
data: {
username: 'admin',
password: 'password',
role: 'admin',
}
}
]

View File

@ -21,8 +21,10 @@ const runCli = async () => {
);
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

@ -9,6 +9,7 @@ import { User } from './classes.user.js';
export interface IJwtData {
userId: string;
status: 'loggedIn' | 'loggedOut';
expiresAt: number;
}
export class CloudlyAuthManager {
@ -27,18 +28,22 @@ export class CloudlyAuthManager {
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('jwtKeys');
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('jwtKeys', newJwtKeys);
await kvStore.writeKey('jwtKeypair', newJwtKeys);
} else {
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
}
@ -48,18 +53,28 @@ export class CloudlyAuthManager {
'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,
},
};
}
)
@ -68,14 +83,33 @@ export class CloudlyAuthManager {
public async stop () {}
public adminJwtGuard = new plugins.smartguard.Guard<{jwt: string}>(async (dataArg) => {
const jwt = dataArg.jwt;
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.'
failedHint: 'user is not admin.',
name: 'adminIdentityGuard',
})
}

View File

@ -5,6 +5,26 @@ 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: {
@ -14,14 +34,17 @@ export class User extends plugins.smartdata.SmartDataDbDoc<
});
}
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: {
role: 'admin' | 'user';
username: string;
password: string;
};
public data: plugins.servezoneInterfaces.data.IUser['data'];
}

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

@ -3,7 +3,8 @@ 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: [],
@ -54,6 +54,17 @@ export class ClusterManager {
};
})
);
// delete cluster
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_DeleteCluster>(
new plugins.typedrequest.TypedHandler('deleteCluster', async (reqDataArg, toolsArg) => {
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqDataArg);
await this.deleteCluster(reqDataArg.clusterId);
return {
success: true,
};
})
);
}
public async init() {
@ -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 '../plugins.js';
import { Cloudly } from '../classes.cloudly.js';
import type { Cluster } from '../manager.cluster/classes.cluster.js';
/**
* in charge of talking to coreflow services on clusters
@ -13,24 +14,42 @@ export class CloudlyCoreflowManager {
this.cloudlyRef = cloudlyRefArg;
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByJumpCode>(
new plugins.typedrequest.TypedHandler('getIdentityByJumpCode', async (requestData) => {
const clusterConfig =
await this.cloudlyRef.clusterManager.getClusterConfigBy_JumpCode(
requestData.jumpCode
);
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken>(
new plugins.typedrequest.TypedHandler('getIdentityByToken', async (requestData) => {
const user = await this.cloudlyRef.authManager.CUser.getInstance({
data: {
tokens: [{
token: requestData.token,
}] // find the proper user here.
} as any
});
if (!clusterConfig) {
throw new plugins.typedrequest.TypedResponseError('The supplied jumpCode is not valid.');
if (!user) {
throw new plugins.typedrequest.TypedResponseError('The supplied token is not valid. No matching user found.');
}
if (user.data.type !== 'machine') {
throw new plugins.typedrequest.TypedResponseError('The supplied token is not valid. The user is not a machine.');
}
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: {
clusterId: clusterConfig.id,
clusterName: clusterConfig.data.name,
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: 'cluster:' + clusterConfig.id, // TODO: create real users for clusters
userId: user.id,
expiresAt: expiryTimestamp,
})
},
};
@ -42,16 +61,16 @@ export class CloudlyCoreflowManager {
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>(
'getClusterConfig',
async (dataArg) => {
const clusterIdentifier = dataArg.clusterIdentifier;
const identity = dataArg.identity;
console.log('trying to get clusterConfigSet');
console.log(dataArg);
const clusterConfigSet =
await this.cloudlyRef.clusterManager.getClusterConfigBy_ClusterIdentifier(
clusterIdentifier
const cluster =
await this.cloudlyRef.clusterManager.getClusterBy_Identity(
identity
);
console.log('got cluster config and sending it back to coreflow');
return {
configData: await clusterConfigSet.createSavableObject(),
configData: await cluster.createSavableObject(),
deploymentDirectives: [],
};
}
@ -60,14 +79,14 @@ 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(),
};

View File

@ -26,4 +26,20 @@ 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,5 +1,6 @@
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';
@ -7,6 +8,8 @@ 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;
@ -14,7 +17,6 @@ export class ImageManager {
public CImage = plugins.smartdata.setDefaultManagerForDoc(this, Image);
constructor(cloudlyRefArg: Cloudly) {
this.cloudlyRef = cloudlyRefArg;
@ -24,7 +26,7 @@ export class ImageManager {
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
'createImage',
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({
name: reqArg.name,
description: reqArg.description,
@ -35,13 +37,25 @@ export class ImageManager {
};
}
)
)
);
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.adminJwtGuard], reqArg);
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
const image = await this.CImage.getInstance({
id: reqArg.imageId,
});
@ -55,7 +69,7 @@ export class ImageManager {
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
'getAllImages',
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({});
return {
images: await Promise.all(
@ -71,9 +85,37 @@ export class ImageManager {
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PushImageVersion>(
'pushImageVersion',
async (reqArg) => {
const pushStream = reqArg.imageStream;
return {};
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 {
allowed: true,
};
}
)
);
@ -83,11 +125,17 @@ export class ImageManager {
'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,
@ -98,6 +146,7 @@ export class ImageManager {
}
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);
@ -105,12 +154,17 @@ export class ImageManager {
this.cloudlyRef.config.data.s3Descriptor
);
const bucket = await this.smartbucketInstance.getBucketByName('cloudly-test');
await bucket.fastPut({ path: 'test/test.txt', contents: 'hello' });
}
await bucket.fastPut({ path: 'images/00init', contents: 'init' });
public async createImage(nameArg: string) {
const newImage = await this.CImage.create({
name: nameArg,
this.imageDir = await bucket.getDirectoryFromPath({
path: '/images',
});
// lets setup dockerstore
await plugins.smartfile.fs.ensureDir(paths.dockerImageStoreDir);
this.dockerImageStore = new plugins.docker.DockerImageStore({
localDirPath: paths.dockerImageStoreDir,
bucketDir: this.imageDir,
});
}
}

View File

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

View File

@ -0,0 +1,87 @@
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,11 +1,13 @@
import * as plugins from '../plugins.js';
import { Cloudly } from '../classes.cloudly.js';
import { Cluster } from '../manager.cluster/cluster.js';
import { Server } from './server.js';
import { Cluster } from '../manager.cluster/classes.cluster.js';
import { Server } from './classes.server.js';
import { CurlFresh } from './classes.curlfresh.js';
export class CloudlyServerManager {
public cloudlyRef: Cloudly;
public typedRouter = new plugins.typedrequest.TypedRouter();
public curlfreshInstance = new CurlFresh(this);
public hetznerAccount: plugins.hetznercloud.HetznerAccount;
@ -63,7 +65,8 @@ 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}`);

View File

@ -0,0 +1,13 @@
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> {
@plugins.smartdata.svDb()
public id: string;
@plugins.smartdata.svDb()
public data: plugins.servezoneInterfaces.data.IService['data'];
}

View File

@ -0,0 +1,18 @@
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;
}
}

View File

@ -2,4 +2,5 @@ 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,9 +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 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';
@ -41,6 +43,7 @@ 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';
@ -53,8 +56,10 @@ export {
smartacme,
smartbucket,
smartcli,
smartclickhouse,
smartdata,
smartexit,
smartexpect,
smartdelay,
smartfile,
smartguard,
@ -65,6 +70,7 @@ export {
smartpromise,
smartrequest,
smartssh,
smartstream,
smartstring,
smartunique,
taskbuffer,

View File

@ -1,8 +1,10 @@
import * as plugins from './plugins.js';
export type TClientType = 'coreflow' | 'cli' | 'serverconfig';
export type TClientType = 'api' | 'ci' | 'coreflow' | 'cli' | 'serverconfig';
export class CloudlyClient {
import { Image } from './classes.image.js';
export class CloudlyApiClient {
private cloudlyUrl: string;
private registerAs: string;
@ -18,9 +20,13 @@ export class CloudlyClient {
plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction['request']
>();
constructor(registerAsArg: TClientType) {
this.cloudlyUrl = process.env.CLOUDLY_URL || 'https://cloudly.layer.io:443';
this.registerAs = registerAsArg;
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}`
@ -33,13 +39,6 @@ export class CloudlyClient {
})
);
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);
@ -55,76 +54,114 @@ export class CloudlyClient {
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 async getIdentityByJumpCode(
jumpCodeArg: string,
tagConnection = false
): Promise<plugins.servezoneInterfaces.data.IClusterIdentifier> {
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_GetIdentityByJumpCode>(
'getIdentityByJumpCode'
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken>(
'getIdentityByToken'
);
console.log(`trying to get identity from cloudly with supplied jumpCodeArg: ${jumpCodeArg}`);
console.log(`trying to get identity from cloudly with supplied jumpCodeArg: ${token}`);
const response = await identityRequest.fire({
jumpCode: jumpCodeArg,
token: token,
});
console.log('got identity response');
const identity = response.clusterIdentifier;
const identity = response.identity;
if (tagConnection) {
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.IClusterIdentifier
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({
jwt: '',
clusterIdentifier: identityArg,
identity: identityArg,
});
return response.configData;
}
/**
* will use statefull identity by default
*/
public async getServerConfigFromCloudlyByIdentity(
identityArg: plugins.servezoneInterfaces.data.IClusterIdentifier
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({
jwt: '', // TODO: do proper auth here
serverId: '' // TODO: get server id here
identity: identityArg,
serverId: '', // TODO: get server id here
});
return response.configData;
}
/**
* gets a certificate for a domain used by a service
* @param serviceNameArg
* @param domainNameArg
*/
public async getCertificateForDomainOverHttps(domainNameArg: string): Promise<plugins.tsclass.network.ICert> {
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_GetSslCertificate>(
'getSslCertificate'
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain>(
'getCertificateForDomain'
);
const typedResponse = await typedCertificateRequest.fire({
authToken: '', // do proper auth here
requiredCertName: domainNameArg,
identity: this.identity, // do proper auth here
domainName: optionsArg.domainName,
type: optionsArg.type,
});
return typedResponse.certificate;
}
public images = {
// Images
getImages: async () => {
return Image.getImages(this);
},
createImage: async (optionsArg: Parameters<typeof Image.createImage>[1]) => {
return Image.createImage(this, optionsArg);
}
}
}

View File

@ -1,7 +1,100 @@
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;
}
/**
* 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

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

View File

@ -6,10 +6,14 @@ export {
}
// @push.rocks scope
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrx from '@push.rocks/smartrx';
import * as webstream from '@push.rocks/smartstream/web';
export {
smartpromise,
smartrx,
webstream,
}
// @api.global scope

View File

@ -0,0 +1,16 @@
{
"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,3 @@
{
"name": "@serve.zone/cli"
}

View File

@ -0,0 +1,10 @@
{
"name": "@serve.zone/interfaces",
"dependencies": [
"@tsclass/tsclass"
],
"registries": [
"registry.npmjs.org:public",
"verdaccio.lossless.digital:public"
]
}

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.1.1',
description: 'A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.'
version: '1.2.0',
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();
export interface ILoginState {
jwt: string;
identity: plugins.interfaces.data.IIdentity;
}
export const loginStatePart = await appstate.getStatePart<ILoginState>(
export const loginStatePart: plugins.smartstate.StatePart<unknown, ILoginState> = await appstate.getStatePart<ILoginState>(
'login',
{ jwt: null },
{ identity: null },
'persistent'
);
@ -22,10 +22,15 @@ export const loginAction = loginStatePart.createAction<{ username: string; passw
const response = await trLogin.fire({
username: payloadArg.username,
password: payloadArg.password,
}).catch(err => {
console.log(err);
return {
...statePartArg.getState(),
}
});
return {
...currentState,
...(response.jwt ? { jwt: response.jwt } : {}),
...(response.identity ? { identity: response.identity } : {}),
};
}
);
@ -34,7 +39,7 @@ export const logoutAction = loginStatePart.createAction(async (statePartArg) =>
const currentState = statePartArg.getState();
return {
...currentState,
jwt: null,
identity: null,
};
});
@ -81,7 +86,7 @@ export const getAllDataAction = dataState.createAction(async (statePartArg, part
'adminGetConfigBundlesAndSecretGroups'
);
const response = await trGetSecrets.fire({
jwt: loginStatePart.getState().jwt,
identity: loginStatePart.getState().identity,
});
currentState = {
...currentState,
@ -95,7 +100,7 @@ export const getAllDataAction = dataState.createAction(async (statePartArg, part
'getAllImages'
);
const responseImages = await trGetImages.fire({
jwt: loginStatePart.getState().jwt,
identity: loginStatePart.getState().identity,
});
currentState = {
...currentState,
@ -109,7 +114,7 @@ export const getAllDataAction = dataState.createAction(async (statePartArg, part
'getAllClusters'
);
const responseClusters = await trGetClusters.fire({
jwt: loginStatePart.getState().jwt,
identity: loginStatePart.getState().identity,
});
currentState = {
@ -130,7 +135,7 @@ export const createSecretGroupAction = dataState.createAction(
'adminCreateConfigBundlesAndSecretGroups'
);
const response = await trCreateSecretGroup.fire({
jwt: loginStatePart.getState().jwt,
identity: loginStatePart.getState().identity,
secretBundles: [],
secretGroups: [payloadArg],
});
@ -149,7 +154,7 @@ export const deleteSecretGroupAction = dataState.createAction(
'adminDeleteConfigBundlesAndSecretGroups'
);
const response = await trDeleteSecretGroup.fire({
jwt: loginStatePart.getState().jwt,
identity: loginStatePart.getState().identity,
secretBundleIds: [],
secretGroupIds: [payloadArg.secretGroupId],
});
@ -168,7 +173,7 @@ export const deleteSecretBundleAction = dataState.createAction(
'adminDeleteConfigBundlesAndSecretGroups'
);
const response = await trDeleteConfigBundle.fire({
jwt: loginStatePart.getState().jwt,
identity: loginStatePart.getState().identity,
secretBundleIds: [payloadArg.configBundleId],
secretGroupIds: [],
});
@ -187,7 +192,7 @@ export const createImageAction = dataState.createAction(
'createImage'
);
const response = await trCreateImage.fire({
jwt: loginStatePart.getState().jwt,
identity: loginStatePart.getState().identity,
name: payloadArg.imageName,
description: payloadArg.description,
});
@ -210,7 +215,7 @@ export const deleteImageAction = dataState.createAction(
'deleteImage'
);
const response = await trDeleteImage.fire({
jwt: loginStatePart.getState().jwt,
identity: loginStatePart.getState().identity,
imageId: payloadArg.imageId,
});
currentState = {
@ -238,7 +243,7 @@ export const addClusterAction = dataState.createAction(
'createCluster'
);
const response = await trAddCluster.fire({
jwt: loginStatePart.getState().jwt,
identity: loginStatePart.getState().identity,
...payloadArg,
});
currentState = {

View File

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

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import {
DeesElement,
@ -31,13 +32,9 @@ export class CloudlyViewBackups extends DeesElement {
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 16px 16px;
}
`,
];

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import {
DeesElement,
@ -31,13 +32,9 @@ export class CloudlyViewClusters extends DeesElement {
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 16px 16px;
}
`,
];

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import {
DeesElement,
@ -31,13 +32,9 @@ export class CloudlyViewDbs extends DeesElement {
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 16px 16px;
}
`,
];

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import {
DeesElement,
@ -31,13 +32,9 @@ export class CloudlyViewDeployments extends DeesElement {
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 16px 16px;
}
`,
];

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import {
DeesElement,
@ -31,13 +32,8 @@ export class CloudlyViewDns extends DeesElement {
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 16px 16px;
}
`,
];

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import { DeesElement, customElement, html, state, css, cssManager } from '@design.estate/dees-element';
@ -23,13 +24,9 @@ export class CloudlyViewImages extends DeesElement {
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 16px 16px;
}
`,
];

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import {
DeesElement,
@ -31,13 +32,9 @@ export class CloudlyViewLogs extends DeesElement {
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 16px 16px;
}
`,
];

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import {
DeesElement,
@ -31,14 +32,8 @@ export class CloudlyViewMails extends DeesElement {
public static styles = [
cssManager.defaultStyles,
css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 16px 16px;
}
`,
shared.viewHostCss,
css``
];
public render() {

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import {
DeesElement,
@ -31,13 +32,8 @@ export class CloudlyViewOverview extends DeesElement {
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 8px 16px;
}
.clusterGrid {
display: grid;
grid-template-columns: ${cssManager.cssGridColumns(3, 8)};

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import {
DeesElement,
@ -31,13 +32,9 @@ export class CloudlyViewS3 extends DeesElement {
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 16px 16px;
}
`,
];

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import {
DeesElement,
@ -31,13 +32,9 @@ export class CloudlyViewSecretBundles extends DeesElement {
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 16px 16px;
}
`,
];

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import { DeesElement, customElement, html, state, css, cssManager } from '@design.estate/dees-element';
@ -23,13 +24,9 @@ export class CloudlyViewSecretGroups extends DeesElement {
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 16px 16px;
}
`,
];

View File

@ -1,4 +1,5 @@
import * as plugins from '../plugins.js';
import * as shared from '../elements/shared/index.js';
import {
DeesElement,
@ -31,13 +32,9 @@ export class CloudlyViewServices extends DeesElement {
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 16px 16px;
}
`,
];

View File

@ -21,6 +21,8 @@ export class CloudlySectionheading extends DeesElement {
h1 {
font-family: 'Cal Sans';
letter-spacing: 0.025em;
margin: 0px;
margin-bottom: 16px;
}
`,
]

View File

@ -0,0 +1,10 @@
import { css } from '@design.estate/dees-element';
export const viewHostCss = css`
:host {
display: block;
margin: auto;
max-width: 1280px;
padding: 16px 16px;
}
`;

View File

@ -1 +1,2 @@
export * from './cloudly-sectionheading.js';
export * from './css.js';

View File

@ -13,7 +13,9 @@ export { deesDomtools, deesElement, deesCatalog };
// @push.rocks scope
import * as webjwt from '@push.rocks/webjwt';
import * as smartstate from '@push.rocks/smartstate';
export {
webjwt,
smartstate,
}