Compare commits

...

8 Commits

16 changed files with 326 additions and 58 deletions

@@ -1,5 +1,33 @@
# Changelog # Changelog
## 2024-12-29 - 4.9.0 - feat(apiclient)
Add external registry management capabilities to Cloudly API client.
- Introduce ExternalRegistry class with methods for getting, creating, and updating external registries.
- Expand requests module to handle external registry management, including creation and deletion.
## 2024-12-28 - 4.8.1 - fix(interfaces)
Fix image location schema in IImage interface
- Refactored the 'external' object within IImage data to a 'location' object.
- Added 'internal' boolean to 'location' to specify internal/external status.
## 2024-12-28 - 4.8.0 - feat(manager.registry)
Add external registry management
- Introduced ExternalRegistry class for handling external registry configurations.
- Updated IExternalRegistry interface to include registry details.
- Enhanced IImage interface to support linking with external registries.
## 2024-12-28 - 4.7.1 - fix(secretmanagement)
Refactor secret bundle actions and improve authorization handling
- Refactored secret bundle handling by renaming methods and reorganizing static and instance methods in SecretBundle class.
- Added getSecretBundleByAuthorization method to SecretBundle.
- Improved getFlatKeyValueObjectForEnvironment to accurately retrieve key-value pairs for specified environments.
- Removed deprecated IEnvBundle interface and related request handler for better clarity and code usage.
- Updated request interfaces related to secret bundles for consistent method naming and arguments.
## 2024-12-22 - 4.7.0 - feat(apiclient) ## 2024-12-22 - 4.7.0 - feat(apiclient)
Add method to flatten secret bundles into key-value objects. Add method to flatten secret bundles into key-value objects.

@@ -1,6 +1,6 @@
{ {
"name": "@serve.zone/cloudly", "name": "@serve.zone/cloudly",
"version": "4.7.0", "version": "4.9.0",
"private": false, "private": false,
"description": "A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.", "description": "A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.",
"type": "module", "type": "module",

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

@@ -0,0 +1,30 @@
import * as plugins from '../plugins.js';
import * as paths from '../paths.js';
import type { Cloudly } from 'ts/classes.cloudly.js';
export class ExternalRegistry extends plugins.smartdata.SmartDataDbDoc<ExternalRegistry, plugins.servezoneInterfaces.data.IExternalRegistry> {
// STATIC
public async getRegistryById(registryNameArg: string) {
this
}
// INSTANCE
public cloudlyRef: Cloudly;
public smartdataDb: plugins.smartdata.SmartdataDb;
@plugins.smartdata.svDb()
public id: string;
@plugins.smartdata.svDb()
public data: plugins.servezoneInterfaces.data.IExternalRegistry['data'];
get db() {
return this.cloudlyRef.mongodbConnector.smartdataDb;
}
constructor(cloudlyRef: Cloudly) {
super();
this.cloudlyRef = cloudlyRef;
}
}

@@ -148,30 +148,26 @@ export class CloudlySecretManager {
); );
this.typedrouter.addTypedHandler( this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetEnvBundle>( new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetFlatKeyValueObject>(
'getEnvBundle', 'getFlatKeyValueObject',
async (dataArg) => { async (dataArg) => {
const wantedBundle = await SecretBundle.getInstance({ const wantedBundle = await SecretBundle.getInstance({
data: { data: {
authorizations: { authorizations: {
// @ts-ignore // @ts-ignore
$elemMatch: { $elemMatch: {
secretAccessKey: dataArg.authorization, secretAccessKey: dataArg.secretBundleAuthorization.secretAccessKey,
}, },
}, },
}, },
}); });
const authorization = await wantedBundle.getAuthorizationFromAuthKey( const authorization = await wantedBundle.getAuthorizationFromAuthKey(
dataArg.authorization, dataArg.secretBundleAuthorization.secretAccessKey,
); );
return { return {
envBundle: { flatKeyValueObject: await wantedBundle.getKeyValueObjectForEnvironment(
configKeyValueObject: await wantedBundle.getKeyValueObjectForEnvironment(
authorization.environment, authorization.environment,
), ),
environment: authorization.environment,
timeSensitive: false,
},
}; };
}, },
), ),

@@ -0,0 +1,84 @@
import * as plugins from './plugins.js';
import type { CloudlyApiClient } from './classes.cloudlyapiclient.js';
export class ExternalRegistry implements plugins.servezoneInterfaces.data.IExternalRegistry {
// STATIC
public static async getRegistryById(cloudlyClientRef: CloudlyApiClient, registryNameArg: string) {
const getRegistryByIdTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.externalRegistry.IReq_GetRegistryById>(
'getExternalRegistryById'
);
const response = await getRegistryByIdTR.fire({
identity: cloudlyClientRef.identity,
registryName: registryNameArg,
});
const newRegistry = new ExternalRegistry(cloudlyClientRef);
Object.assign(newRegistry, response.registry);
return newRegistry;
}
public static async getRegistries(cloudlyClientRef: CloudlyApiClient) {
const getRegistriesTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.externalRegistry.IReq_GetRegistries>(
'getExternalRegistries'
);
const response = await getRegistriesTR.fire({
identity: cloudlyClientRef.identity,
});
const registryConfigs: ExternalRegistry[] = [];
for (const registryConfig of response.registries) {
const newRegistry = new ExternalRegistry(cloudlyClientRef);
Object.assign(newRegistry, registryConfig);
registryConfigs.push(newRegistry);
}
return registryConfigs;
}
public static async createRegistry(cloudlyClientRef: CloudlyApiClient, registryNameArg: string, registryDataArg: Partial<plugins.servezoneInterfaces.data.IExternalRegistry['data']>) {
const createRegistryTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.externalRegistry.IReq_CreateRegistry>(
'createExternalRegistry'
);
const response = await createRegistryTR.fire({
identity: cloudlyClientRef.identity,
registryName: registryNameArg,
registryData: registryDataArg as plugins.servezoneInterfaces.data.IExternalRegistry['data'],
});
const newRegistry = new ExternalRegistry(cloudlyClientRef);
Object.assign(newRegistry, response.registry);
return newRegistry;
}
// INSTANCE
public id: string;
public data: plugins.servezoneInterfaces.data.IExternalRegistry['data'];
public cloudlyClientRef: CloudlyApiClient;
constructor(cloudlyClientRef: CloudlyApiClient) {
this.cloudlyClientRef = cloudlyClientRef;
}
public async update() {
const updateRegistryTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.externalRegistry.IReq_UpdateRegistry>(
'updateExternalRegistry'
);
const response = await updateRegistryTR.fire({
identity: this.cloudlyClientRef.identity,
registryData: this.data,
});
const resultRegistryData = response.resultRegistry.data;
plugins.smartexpect.expect(resultRegistryData).toEqual(this.data);
return this;
}
public async delete(cloudlyClientRef: CloudlyApiClient, registryIdArg: string) {
const deleteRegistryTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.externalRegistry.IReq_DeleteRegistryById>(
'deleteExternalRegistryById'
);
const response = await deleteRegistryTR.fire({
identity: cloudlyClientRef.identity,
registryId: this.id,
});
plugins.smartexpect.expect(response.ok).toBeTrue();
return null;
}
}

@@ -1,16 +1,9 @@
import * as plugins from './plugins.js'; import * as plugins from './plugins.js';
import type { CloudlyApiClient } from './classes.cloudlyapiclient.js'; import type { CloudlyApiClient } from './classes.cloudlyapiclient.js';
import { SecretGroup } from './classes.secretgroup.js';
export class SecretBundle implements plugins.servezoneInterfaces.data.ISecretBundle { export class SecretBundle implements plugins.servezoneInterfaces.data.ISecretBundle {
public cloudlyClientRef: CloudlyApiClient; // STATIC
public id: string;
public data: plugins.servezoneInterfaces.data.ISecretBundle['data'];
constructor(cloudlyClientRef: CloudlyApiClient) {
this.cloudlyClientRef = cloudlyClientRef;
}
public static async getSecretBundleById(cloudlyClientRef: CloudlyApiClient, secretBundleIdArg: string) { public static async getSecretBundleById(cloudlyClientRef: CloudlyApiClient, secretBundleIdArg: string) {
const getSecretBundleByIdTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetSecretBundleById>( const getSecretBundleByIdTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetSecretBundleById>(
'getSecretBundleById' 'getSecretBundleById'
@@ -24,6 +17,19 @@ export class SecretBundle implements plugins.servezoneInterfaces.data.ISecretBun
return newSecretBundle; return newSecretBundle;
} }
public static async getSecretBundleByAuthorization(cloudlyClientRef: CloudlyApiClient, secretBundleAuthorizationArg: plugins.servezoneInterfaces.data.ISecretBundleAuthorization) {
const getSecretBundleByAuthorizationTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetSecretBundleByAuthorization>(
'getSecretBundleByAuthorization'
);
const response = await getSecretBundleByAuthorizationTR.fire({
identity: cloudlyClientRef.identity,
secretBundleAuthorization: secretBundleAuthorizationArg,
});
const newSecretBundle = new SecretBundle(cloudlyClientRef);
Object.assign(newSecretBundle, response.secretBundle);
return newSecretBundle;
}
public static async getSecretBundles(cloudlyClientRef: CloudlyApiClient) { public static async getSecretBundles(cloudlyClientRef: CloudlyApiClient) {
const getSecretBundlesTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetSecretBundles>( const getSecretBundlesTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetSecretBundles>(
'getSecretBundles' 'getSecretBundles'
@@ -64,6 +70,17 @@ export class SecretBundle implements plugins.servezoneInterfaces.data.ISecretBun
return newSecretBundle; return newSecretBundle;
} }
// INSTANCE
public cloudlyClientRef: CloudlyApiClient;
public id: string;
public data: plugins.servezoneInterfaces.data.ISecretBundle['data'];
constructor(cloudlyClientRef: CloudlyApiClient) {
this.cloudlyClientRef = cloudlyClientRef;
}
public async update() { public async update() {
const updateSecretBundleTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_UpdateSecretBundle>( const updateSecretBundleTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_UpdateSecretBundle>(
'updateSecretBundle' 'updateSecretBundle'
@@ -94,9 +111,25 @@ export class SecretBundle implements plugins.servezoneInterfaces.data.ISecretBun
return null; return null;
} }
public async toFlatKeyValueObject() { public async getFlatKeyValueObjectForEnvironment(environmentArg: string = 'production') {
return { const bundleAuthorization = this.data.authorizations.find(authorization => {
// TODO: implement return authorization.environment === environmentArg;
}; });
if (bundleAuthorization) {
throw new Error(`no matching environment >>${environmentArg} found in secret bundle`);
}
const getFlatKeyValueObjectTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetFlatKeyValueObject>(
'getFlatKeyValueObject'
);
const response = await getFlatKeyValueObjectTR.fire({
identity: this.cloudlyClientRef.identity,
seccretBundleId: this.id,
secretBundleAuthorization: bundleAuthorization,
});
const flatKeyValueObject: {[key: string]: string} = response.flatKeyValueObject;
return flatKeyValueObject;
} }
} }

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

@@ -0,0 +1,12 @@
import * as plugins from '../plugins.js';
export interface IExternalRegistry {
id: string;
data: {
type: 'docker' | 'npm';
name: string;
url: string;
username: string;
password: string;
};
}

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

@@ -3,8 +3,8 @@ export * from './cluster.js';
export * from './config.js'; export * from './config.js';
export * from './deployment.js'; export * from './deployment.js';
export * from './docker.js'; export * from './docker.js';
export * from './env.js';
export * from './event.js'; export * from './event.js';
export * from './externalregistry.js';
export * from './image.js'; export * from './image.js';
export * from './secretbundle.js'; export * from './secretbundle.js';
export * from './secretgroup.js' export * from './secretgroup.js'

@@ -45,9 +45,11 @@ export interface ISecretBundle {
/** /**
* authrozations select a specific environment of a config bundle * authrozations select a specific environment of a config bundle
*/ */
authorizations: Array<{ authorizations: Array<ISecretBundleAuthorization>;
secretAccessKey: string;
environment: string;
}>;
}; };
} }
export interface ISecretBundleAuthorization {
secretAccessKey: string;
environment: string;
}

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

@@ -4,6 +4,7 @@ import * as adminRequests from './admin.js';
import * as certificateRequests from './certificate.js'; import * as certificateRequests from './certificate.js';
import * as clusterRequests from './cluster.js'; import * as clusterRequests from './cluster.js';
import * as configRequests from './config.js'; import * as configRequests from './config.js';
import * as externalRegistryRequests from './externalregistry.js';
import * as identityRequests from './identity.js'; import * as identityRequests from './identity.js';
import * as imageRequests from './image.js'; import * as imageRequests from './image.js';
import * as informRequests from './inform.js'; import * as informRequests from './inform.js';
@@ -22,6 +23,7 @@ export {
certificateRequests as certificate, certificateRequests as certificate,
clusterRequests as cluster, clusterRequests as cluster,
configRequests as config, configRequests as config,
externalRegistryRequests as externalRegistry,
identityRequests as identity, identityRequests as identity,
imageRequests as image, imageRequests as image,
informRequests as inform, informRequests as inform,

@@ -2,26 +2,6 @@ import * as plugins from '../plugins.js';
import * as data from '../data/index.js'; import * as data from '../data/index.js';
import * as userInterfaces from '../data/user.js'; import * as userInterfaces from '../data/user.js';
/**
* when retrieving secrets for actual use, you do this in the form of an envBundle.
*/
export interface IReq_GetEnvBundle extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetEnvBundle
> {
method: 'getEnvBundle';
request: {
authorization: string;
/**
* specify this if you want to get a warning, if the envBundle is for an unexpected environment
*/
environment?: string;
};
response: {
envBundle: data.IEnvBundle;
};
}
export interface IReq_GetSecretBundles extends plugins.typedrequestInterfaces.implementsTR< export interface IReq_GetSecretBundles extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest, plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetSecretBundles IReq_GetSecretBundles
@@ -92,3 +72,32 @@ export interface IReq_DeleteSecretBundleById extends plugins.typedrequestInterfa
ok: boolean; ok: boolean;
}; };
} }
export interface IReq_GetSecretBundleByAuthorization extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetSecretBundleByAuthorization
> {
method: 'getSecretBundleByAuthorization';
request: {
identity: userInterfaces.IIdentity;
secretBundleAuthorization: data.ISecretBundleAuthorization;
};
response: {
secretBundle: data.ISecretBundle;
};
}
export interface IReq_GetFlatKeyValueObject extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetFlatKeyValueObject
> {
method: 'getFlatKeyValueObject';
request: {
identity: userInterfaces.IIdentity;
seccretBundleId: string;
secretBundleAuthorization: data.ISecretBundleAuthorization;
};
response: {
flatKeyValueObject: {[key: string]: string};
};
}

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