fix(secret-management): Refactor secret management to use distinct secret bundle and group APIs. Introduce API client classes for secret bundles and groups.
This commit is contained in:
parent
d453da709f
commit
4b993fc6b3
@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2024-12-21 - 4.5.3 - fix(secret-management)
|
||||||
|
Refactor secret management to use distinct secret bundle and group APIs. Introduce API client classes for secret bundles and groups.
|
||||||
|
|
||||||
|
- Updated secret management logic to separate secret bundle and group APIs.
|
||||||
|
- Implemented new API client classes for managing secret bundles and groups.
|
||||||
|
- Fixed incorrect method usages for secret-related actions.
|
||||||
|
|
||||||
## 2024-12-20 - 4.5.2 - fix(apiclient)
|
## 2024-12-20 - 4.5.2 - fix(apiclient)
|
||||||
Implemented IService interface in Service class and improved secret bundle documentation.
|
Implemented IService interface in Service class and improved secret bundle documentation.
|
||||||
|
|
||||||
|
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@ -6569,7 +6569,9 @@ snapshots:
|
|||||||
'@push.rocks/smartshell': 3.2.2
|
'@push.rocks/smartshell': 3.2.2
|
||||||
'@push.rocks/taskbuffer': 3.1.7
|
'@push.rocks/taskbuffer': 3.1.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
- supports-color
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
'@hapi/bourne@3.0.0': {}
|
'@hapi/bourne@3.0.0': {}
|
||||||
|
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/cloudly',
|
name: '@serve.zone/cloudly',
|
||||||
version: '4.5.2',
|
version: '4.5.3',
|
||||||
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.'
|
||||||
}
|
}
|
||||||
|
@ -35,20 +35,71 @@ export class CloudlySecretManager {
|
|||||||
this.typedrouter = new plugins.typedrequest.TypedRouter();
|
this.typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||||
|
|
||||||
this.typedrouter.addTypedHandler(
|
// secretbundle routes
|
||||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
|
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetSecretBundles>(
|
||||||
'adminGetConfigBundlesAndSecretGroups',
|
new plugins.typedrequest.TypedHandler(
|
||||||
|
'getSecretBundles',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], dataArg);
|
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], dataArg);
|
||||||
dataArg.identity.jwt;
|
dataArg.identity.jwt;
|
||||||
const secretBundles = await SecretBundle.getInstances({});
|
const secretBundles = await SecretBundle.getInstances({});
|
||||||
const secretGroups = await SecretGroup.getInstances({});
|
|
||||||
return {
|
return {
|
||||||
secretBundles: [
|
secretBundles: [
|
||||||
...(await Promise.all(
|
...(await Promise.all(
|
||||||
secretBundles.map((configBundle) => configBundle.createSavableObject()),
|
secretBundles.map((configBundle) => configBundle.createSavableObject()),
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_CreateSecretBundle>(
|
||||||
|
new plugins.typedrequest.TypedHandler('createSecretBundle', async (dataArg) => {
|
||||||
|
const secretBundle = new SecretBundle();
|
||||||
|
secretBundle.id = plugins.smartunique.shortId(8);
|
||||||
|
secretBundle.data = dataArg.secretBundle.data;
|
||||||
|
await secretBundle.save();
|
||||||
|
return {
|
||||||
|
resultSecretBundle: await secretBundle.createSavableObject(),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_UpdateSecretBundle>(
|
||||||
|
new plugins.typedrequest.TypedHandler('updateSecretBundle', async (dataArg) => {
|
||||||
|
const secretBundle = await SecretBundle.getInstance({
|
||||||
|
id: dataArg.secretBundle.id,
|
||||||
|
});
|
||||||
|
secretBundle.data = dataArg.secretBundle.data;
|
||||||
|
await secretBundle.save();
|
||||||
|
return {
|
||||||
|
resultSecretBundle: await secretBundle.createSavableObject(),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_DeleteSecretBundleById>(
|
||||||
|
new plugins.typedrequest.TypedHandler('deleteSecretBundleById', async (dataArg) => {
|
||||||
|
const secretBundle = await SecretBundle.getInstance({
|
||||||
|
id: dataArg.secretBundleId,
|
||||||
|
});
|
||||||
|
await secretBundle.delete();
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// secretgroup routes
|
||||||
|
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretgroup.IReq_GetSecretGroups>(
|
||||||
|
new plugins.typedrequest.TypedHandler(
|
||||||
|
'getSecretGroups',
|
||||||
|
async (dataArg, toolsArg) => {
|
||||||
|
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], dataArg);
|
||||||
|
dataArg.identity.jwt;
|
||||||
|
const secretGroups = await SecretGroup.getInstances({});
|
||||||
|
return {
|
||||||
secretGroups: [
|
secretGroups: [
|
||||||
...(await Promise.all(
|
...(await Promise.all(
|
||||||
secretGroups.map((secretGroup) => secretGroup.createSavableObject()),
|
secretGroups.map((secretGroup) => secretGroup.createSavableObject()),
|
||||||
@ -59,50 +110,45 @@ export class CloudlySecretManager {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_CreateConfigBundlesAndSecretGroups>(
|
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretgroup.IReq_CreateSecretGroup>(
|
||||||
new plugins.typedrequest.TypedHandler(
|
new plugins.typedrequest.TypedHandler('createSecretGroup', async (dataArg) => {
|
||||||
'adminCreateConfigBundlesAndSecretGroups',
|
const secretGroup = new SecretGroup();
|
||||||
async (dataArg) => {
|
secretGroup.id = plugins.smartunique.shortId(8);
|
||||||
for (const secretGroupObject of dataArg.secretGroups) {
|
secretGroup.data = dataArg.secretGroup.data;
|
||||||
const secretGroup = new SecretGroup();
|
await secretGroup.save();
|
||||||
secretGroup.id = plugins.smartunique.shortId(8);
|
return {
|
||||||
secretGroup.data = secretGroupObject.data;
|
resultSecretGroup: await secretGroup.createSavableObject(),
|
||||||
await secretGroup.save();
|
};
|
||||||
}
|
}),
|
||||||
return {
|
);
|
||||||
ok: true,
|
|
||||||
};
|
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretgroup.IReq_UpdateSecretGroup>(
|
||||||
},
|
new plugins.typedrequest.TypedHandler('updateSecretGroup', async (dataArg) => {
|
||||||
),
|
const secretGroup = await SecretGroup.getInstance({
|
||||||
|
id: dataArg.secretGroup.id,
|
||||||
|
});
|
||||||
|
secretGroup.data = dataArg.secretGroup.data;
|
||||||
|
await secretGroup.save();
|
||||||
|
return {
|
||||||
|
resultSecretGroup: await secretGroup.createSavableObject(),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secretgroup.IReq_DeleteSecretGroupById>(
|
||||||
|
new plugins.typedrequest.TypedHandler('deleteSecretGroupById', async (dataArg) => {
|
||||||
|
const secretGroup = await SecretGroup.getInstance({
|
||||||
|
id: dataArg.secretGroupId,
|
||||||
|
});
|
||||||
|
await secretGroup.delete();
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
};
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.typedrouter.addTypedHandler(
|
this.typedrouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_DeleteConfigBundlesAndSecretGroups>(
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetEnvBundle>(
|
||||||
'adminDeleteConfigBundlesAndSecretGroups',
|
|
||||||
async (dataArg) => {
|
|
||||||
for (const secretGroupId of dataArg.secretGroupIds) {
|
|
||||||
const secretGroup = await SecretGroup.getInstance({
|
|
||||||
id: secretGroupId,
|
|
||||||
});
|
|
||||||
await secretGroup.delete();
|
|
||||||
}
|
|
||||||
for (const secretBundleId of dataArg.secretBundleIds) {
|
|
||||||
const configBundle = await SecretBundle.getInstance({
|
|
||||||
id: secretBundleId,
|
|
||||||
});
|
|
||||||
await configBundle.delete();
|
|
||||||
console.log(`deleted configbundle ${secretBundleId}`);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// lets add typedrouter routes for accessing the configvailt from apps
|
|
||||||
this.typedrouter.addTypedHandler(
|
|
||||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_GetEnvBundle>(
|
|
||||||
'getEnvBundle',
|
'getEnvBundle',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
const wantedBundle = await SecretBundle.getInstance({
|
const wantedBundle = await SecretBundle.getInstance({
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import type { CloudlyApiClient } from './classes.cloudlyapiclient.js';
|
||||||
|
|
||||||
|
export class SecretBundle implements plugins.servezoneInterfaces.data.ISecretBundle {
|
||||||
|
public cloudlyClientRef: CloudlyApiClient;
|
||||||
|
|
||||||
|
public id: string;
|
||||||
|
public data: plugins.servezoneInterfaces.data.ISecretBundle['data'];
|
||||||
|
|
||||||
|
constructor(cloudlyClientRef: CloudlyApiClient) {
|
||||||
|
this.cloudlyClientRef = cloudlyClientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getSecretBundleById(cloudlyClientRef: CloudlyApiClient, secretBundleIdArg: string) {
|
||||||
|
const getSecretBundleByIdTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetSecretBundleById>(
|
||||||
|
'getSecretBundleById'
|
||||||
|
);
|
||||||
|
const response = await getSecretBundleByIdTR.fire({
|
||||||
|
identity: cloudlyClientRef.identity,
|
||||||
|
secretBundleId: secretBundleIdArg,
|
||||||
|
});
|
||||||
|
const newSecretBundle = new SecretBundle(cloudlyClientRef);
|
||||||
|
Object.assign(newSecretBundle, response.secretBundle);
|
||||||
|
return newSecretBundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getSecretBundles(cloudlyClientRef: CloudlyApiClient) {
|
||||||
|
const getSecretBundlesTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_GetSecretBundles>(
|
||||||
|
'getSecretBundles'
|
||||||
|
);
|
||||||
|
const response = await getSecretBundlesTR.fire({
|
||||||
|
identity: cloudlyClientRef.identity,
|
||||||
|
});
|
||||||
|
const secretBundles: SecretBundle[] = [];
|
||||||
|
for (const secretBundle of response.secretBundles) {
|
||||||
|
const newSecretBundle = new SecretBundle(cloudlyClientRef);
|
||||||
|
Object.assign(newSecretBundle, secretBundle);
|
||||||
|
secretBundles.push(newSecretBundle);
|
||||||
|
}
|
||||||
|
return secretBundles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async createSecretBundle(cloudlyClientRef: CloudlyApiClient, secretBundleDataArg: Partial<plugins.servezoneInterfaces.data.ISecretBundle['data']>) {
|
||||||
|
const createSecretBundleTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_CreateSecretBundle>(
|
||||||
|
'createSecretBundle'
|
||||||
|
);
|
||||||
|
const response = await createSecretBundleTR.fire({
|
||||||
|
identity: cloudlyClientRef.identity,
|
||||||
|
secretBundle: {
|
||||||
|
id: null,
|
||||||
|
data: {
|
||||||
|
name: secretBundleDataArg.name,
|
||||||
|
description: secretBundleDataArg.description,
|
||||||
|
type: secretBundleDataArg.type,
|
||||||
|
authorizations: secretBundleDataArg.authorizations,
|
||||||
|
includedImages: secretBundleDataArg.includedImages,
|
||||||
|
includedSecretGroupIds: secretBundleDataArg.includedSecretGroupIds,
|
||||||
|
includedTags: secretBundleDataArg.includedTags,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const newSecretBundle = new SecretBundle(cloudlyClientRef);
|
||||||
|
Object.assign(newSecretBundle, response.resultSecretBundle);
|
||||||
|
return newSecretBundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update() {
|
||||||
|
const updateSecretBundleTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_UpdateSecretBundle>(
|
||||||
|
'updateSecretBundle'
|
||||||
|
);
|
||||||
|
const response = await updateSecretBundleTR.fire({
|
||||||
|
identity: this.cloudlyClientRef.identity,
|
||||||
|
secretBundle: {
|
||||||
|
id: this.id,
|
||||||
|
data: this.data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultSecretBundleData = response.resultSecretBundle.data;
|
||||||
|
plugins.smartexpect.expect(resultSecretBundleData).toEqual(this.data);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(cloudlyClientRef: CloudlyApiClient, secretBundleIdArg: string) {
|
||||||
|
const deleteSecretBundleTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretbundle.IReq_DeleteSecretBundleById>(
|
||||||
|
'deleteSecretBundleById'
|
||||||
|
);
|
||||||
|
const response = await deleteSecretBundleTR.fire({
|
||||||
|
identity: cloudlyClientRef.identity,
|
||||||
|
secretBundleId: this.id,
|
||||||
|
});
|
||||||
|
plugins.smartexpect.expect(response.ok).toBeTrue();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import type { CloudlyApiClient } from './classes.cloudlyapiclient.js';
|
||||||
|
|
||||||
|
export class SecretGroup implements plugins.servezoneInterfaces.data.ISecretGroup {
|
||||||
|
public cloudlyClientRef: CloudlyApiClient;
|
||||||
|
|
||||||
|
public id: string;
|
||||||
|
public data: plugins.servezoneInterfaces.data.ISecretGroup['data'];
|
||||||
|
|
||||||
|
constructor(cloudlyClientRef: CloudlyApiClient) {
|
||||||
|
this.cloudlyClientRef = cloudlyClientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getSecretGroupById(cloudlyClientRef: CloudlyApiClient, secretGroupIdArg: string) {
|
||||||
|
const getSecretGroupByIdTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretgroup.IReq_GetSecretGroupById>(
|
||||||
|
'getSecretGroupById'
|
||||||
|
);
|
||||||
|
const response = await getSecretGroupByIdTR.fire({
|
||||||
|
identity: cloudlyClientRef.identity,
|
||||||
|
secretGroupId: secretGroupIdArg,
|
||||||
|
});
|
||||||
|
const newSecretGroup = new SecretGroup(cloudlyClientRef);
|
||||||
|
Object.assign(newSecretGroup, response.secretGroup);
|
||||||
|
return newSecretGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getSecretGroups(cloudlyClientRef: CloudlyApiClient) {
|
||||||
|
const getSecretGroupsTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretgroup.IReq_GetSecretGroups>(
|
||||||
|
'getSecretGroups'
|
||||||
|
);
|
||||||
|
const response = await getSecretGroupsTR.fire({
|
||||||
|
identity: cloudlyClientRef.identity,
|
||||||
|
});
|
||||||
|
const secretGroups: SecretGroup[] = [];
|
||||||
|
for (const secretGroup of response.secretGroups) {
|
||||||
|
const newSecretGroup = new SecretGroup(cloudlyClientRef);
|
||||||
|
Object.assign(newSecretGroup, secretGroup);
|
||||||
|
secretGroups.push(newSecretGroup);
|
||||||
|
}
|
||||||
|
return secretGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async createSecretGroup(cloudlyClientRef: CloudlyApiClient, secretGroupDataArg: Partial<plugins.servezoneInterfaces.data.ISecretGroup['data']>) {
|
||||||
|
const createSecretGroupTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretgroup.IReq_CreateSecretGroup>(
|
||||||
|
'createSecretGroup'
|
||||||
|
);
|
||||||
|
const response = await createSecretGroupTR.fire({
|
||||||
|
identity: cloudlyClientRef.identity,
|
||||||
|
secretGroup: {
|
||||||
|
id: null,
|
||||||
|
data: {
|
||||||
|
name: secretGroupDataArg.name,
|
||||||
|
description: secretGroupDataArg.description,
|
||||||
|
environments: secretGroupDataArg.environments,
|
||||||
|
key: secretGroupDataArg.key,
|
||||||
|
tags: secretGroupDataArg.tags,
|
||||||
|
priority: secretGroupDataArg.priority,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const newSecretGroup = new SecretGroup(cloudlyClientRef);
|
||||||
|
Object.assign(newSecretGroup, response.resultSecretGroup);
|
||||||
|
return newSecretGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INSTANCE
|
||||||
|
public async update() {
|
||||||
|
const updateSecretGroupTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretgroup.IReq_UpdateSecretGroup>(
|
||||||
|
'updateSecretGroup'
|
||||||
|
);
|
||||||
|
const response = await updateSecretGroupTR.fire({
|
||||||
|
identity: this.cloudlyClientRef.identity,
|
||||||
|
secretGroup: {
|
||||||
|
id: this.id,
|
||||||
|
data: {
|
||||||
|
name: this.data.name,
|
||||||
|
description: this.data.description,
|
||||||
|
environments: this.data.environments,
|
||||||
|
key: this.data.key,
|
||||||
|
tags: this.data.tags,
|
||||||
|
priority: this.data.priority,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultSecretGroupData = response.resultSecretGroup.data;
|
||||||
|
plugins.smartexpect.expect(resultSecretGroupData).toEqual(this.data);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(cloudlyClientRef: CloudlyApiClient, secretGroupIdArg: string) {
|
||||||
|
const deleteSecretGroupTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.secretgroup.IReq_DeleteSecretGroupById>(
|
||||||
|
'deleteSecretGroupById'
|
||||||
|
);
|
||||||
|
const response = await deleteSecretGroupTR.fire({
|
||||||
|
identity: cloudlyClientRef.identity,
|
||||||
|
secretGroupId: this.id,
|
||||||
|
});
|
||||||
|
plugins.smartexpect.expect(response.ok).toBeTrue();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
27
ts_apiclient/classes.secretmanager.ts
Normal file
27
ts_apiclient/classes.secretmanager.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import type { CloudlyApiClient } from './classes.cloudlyapiclient.js';
|
||||||
|
|
||||||
|
import { SecretBundle } from './classes.secretbundle.js';
|
||||||
|
import { SecretGroup } from './classes.secretgroup.js';
|
||||||
|
|
||||||
|
export class SecretManager {
|
||||||
|
// INSTANCE
|
||||||
|
cloudlyClientRef: CloudlyApiClient;
|
||||||
|
|
||||||
|
constructor(cloudlyClientRef: CloudlyApiClient) {
|
||||||
|
this.cloudlyClientRef = cloudlyClientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getSecretGroupsAndBundles() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The secret group has a secret bundle.
|
||||||
|
* This function essentially returns the secret bundle as a flat object.
|
||||||
|
* In other words, it resolves secret groups and
|
||||||
|
*/
|
||||||
|
public async getSecretBundleAsFlatObject(environmentArg: string = 'production') {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -6,11 +6,13 @@ export {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @push.rocks scope
|
// @push.rocks scope
|
||||||
|
import * as smartexpect from '@push.rocks/smartexpect';
|
||||||
import * as smartpromise from '@push.rocks/smartpromise';
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
import * as smartrx from '@push.rocks/smartrx';
|
import * as smartrx from '@push.rocks/smartrx';
|
||||||
import * as webstream from '@push.rocks/smartstream/web';
|
import * as webstream from '@push.rocks/smartstream/web';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
smartexpect,
|
||||||
smartpromise,
|
smartpromise,
|
||||||
smartrx,
|
smartrx,
|
||||||
webstream,
|
webstream,
|
||||||
|
16
ts_interfaces/requests/admin.ts
Normal file
16
ts_interfaces/requests/admin.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
import * as userInterfaces from '../data/user.js';
|
||||||
|
|
||||||
|
export interface IReq_Admin_LoginWithUsernameAndPassword extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_Admin_LoginWithUsernameAndPassword
|
||||||
|
> {
|
||||||
|
method: 'adminLoginWithUsernameAndPassword';
|
||||||
|
request: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
identity: userInterfaces.IIdentity;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
|
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';
|
||||||
@ -9,13 +10,15 @@ import * as informRequests from './inform.js';
|
|||||||
import * as logRequests from './log.js';
|
import * as logRequests from './log.js';
|
||||||
import * as networkRequests from './network.js';
|
import * as networkRequests from './network.js';
|
||||||
import * as routingRequests from './routing.js';
|
import * as routingRequests from './routing.js';
|
||||||
import * as secretRequests from './secret.js';
|
import * as secretBundleRequests from './secretbundle.js';
|
||||||
|
import * as secretGroupRequests from './secretgroup.js';
|
||||||
import * as serverRequests from './server.js';
|
import * as serverRequests from './server.js';
|
||||||
import * as serviceRequests from './service.js';
|
import * as serviceRequests from './service.js';
|
||||||
import * as statusRequests from './status.js';
|
import * as statusRequests from './status.js';
|
||||||
import * as versionRequests from './version.js';
|
import * as versionRequests from './version.js';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
adminRequests as admin,
|
||||||
certificateRequests as certificate,
|
certificateRequests as certificate,
|
||||||
clusterRequests as cluster,
|
clusterRequests as cluster,
|
||||||
configRequests as config,
|
configRequests as config,
|
||||||
@ -25,7 +28,8 @@ export {
|
|||||||
logRequests as log,
|
logRequests as log,
|
||||||
networkRequests as network,
|
networkRequests as network,
|
||||||
routingRequests as routing,
|
routingRequests as routing,
|
||||||
secretRequests as secret,
|
secretBundleRequests as secretbundle,
|
||||||
|
secretGroupRequests as secretgroup,
|
||||||
serverRequests as server,
|
serverRequests as server,
|
||||||
serviceRequests as service,
|
serviceRequests as service,
|
||||||
statusRequests as status,
|
statusRequests as status,
|
||||||
|
@ -1,94 +0,0 @@
|
|||||||
import * as plugins from '../plugins.js';
|
|
||||||
import * as data from '../data/index.js';
|
|
||||||
import * as userInterfaces from '../data/user.js';
|
|
||||||
|
|
||||||
export interface IReq_GetEnvBundle extends plugins.typedrequestInterfaces.implementsTR<
|
|
||||||
plugins.typedrequestInterfaces.ITypedRequest,
|
|
||||||
IReq_GetEnvBundle
|
|
||||||
> {
|
|
||||||
method: 'getEnvBundle';
|
|
||||||
request: {
|
|
||||||
authorization: string;
|
|
||||||
/**
|
|
||||||
* specify this if you want to get a warning, if the envBundle is for an unexpected environment
|
|
||||||
*/
|
|
||||||
environment?: string;
|
|
||||||
};
|
|
||||||
response: {
|
|
||||||
envBundle: data.IEnvBundle;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IReq_Admin_LoginWithUsernameAndPassword extends plugins.typedrequestInterfaces.implementsTR<
|
|
||||||
plugins.typedrequestInterfaces.ITypedRequest,
|
|
||||||
IReq_Admin_LoginWithUsernameAndPassword
|
|
||||||
> {
|
|
||||||
method: 'adminLoginWithUsernameAndPassword';
|
|
||||||
request: {
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
response: {
|
|
||||||
identity: userInterfaces.IIdentity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IReq_Admin_GetConfigBundlesAndSecretGroups extends plugins.typedrequestInterfaces.implementsTR<
|
|
||||||
plugins.typedrequestInterfaces.ITypedRequest,
|
|
||||||
IReq_Admin_GetConfigBundlesAndSecretGroups
|
|
||||||
> {
|
|
||||||
method: 'adminGetConfigBundlesAndSecretGroups';
|
|
||||||
request: {
|
|
||||||
identity: userInterfaces.IIdentity;
|
|
||||||
};
|
|
||||||
response: {
|
|
||||||
secretBundles: data.ISecretBundle[];
|
|
||||||
secretGroups: data.ISecretGroup[];
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IReq_Admin_CreateConfigBundlesAndSecretGroups extends plugins.typedrequestInterfaces.implementsTR<
|
|
||||||
plugins.typedrequestInterfaces.ITypedRequest,
|
|
||||||
IReq_Admin_CreateConfigBundlesAndSecretGroups
|
|
||||||
> {
|
|
||||||
method: 'adminCreateConfigBundlesAndSecretGroups';
|
|
||||||
request: {
|
|
||||||
identity: userInterfaces.IIdentity;
|
|
||||||
secretBundles: data.ISecretBundle[];
|
|
||||||
secretGroups: data.ISecretGroup[];
|
|
||||||
};
|
|
||||||
response: {
|
|
||||||
ok: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IReq_Admin_UpdateConfigBundlesAndSecretGroups extends plugins.typedrequestInterfaces.implementsTR<
|
|
||||||
plugins.typedrequestInterfaces.ITypedRequest,
|
|
||||||
IReq_Admin_UpdateConfigBundlesAndSecretGroups
|
|
||||||
> {
|
|
||||||
method: 'adminUpdateConfigBundlesAndSecretGroups';
|
|
||||||
request: {
|
|
||||||
identity: userInterfaces.IIdentity;
|
|
||||||
configBundles: data.ISecretBundle[];
|
|
||||||
secretGroups: data.ISecretGroup[];
|
|
||||||
};
|
|
||||||
response: {
|
|
||||||
ok: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IReq_Admin_DeleteConfigBundlesAndSecretGroups extends plugins.typedrequestInterfaces.implementsTR<
|
|
||||||
plugins.typedrequestInterfaces.ITypedRequest,
|
|
||||||
IReq_Admin_DeleteConfigBundlesAndSecretGroups
|
|
||||||
> {
|
|
||||||
method: 'adminDeleteConfigBundlesAndSecretGroups';
|
|
||||||
request: {
|
|
||||||
identity: userInterfaces.IIdentity;
|
|
||||||
secretBundleIds: string[];
|
|
||||||
secretGroupIds: string[];
|
|
||||||
};
|
|
||||||
response: {
|
|
||||||
ok: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
79
ts_interfaces/requests/secretbundle.ts
Normal file
79
ts_interfaces/requests/secretbundle.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
import * as data from '../data/index.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<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_GetSecretBundles
|
||||||
|
> {
|
||||||
|
method: 'getSecretBundles';
|
||||||
|
request: {
|
||||||
|
identity: userInterfaces.IIdentity;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
secretBundles: data.ISecretBundle[];
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_CreateSecretBundle extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_CreateSecretBundle
|
||||||
|
> {
|
||||||
|
method: 'createSecretBundle';
|
||||||
|
request: {
|
||||||
|
identity: userInterfaces.IIdentity;
|
||||||
|
secretBundle: data.ISecretBundle;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
resultSecretBundle: data.ISecretBundle;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_UpdateSecretBundle extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_UpdateSecretBundle
|
||||||
|
> {
|
||||||
|
method: 'updateSecretBundle';
|
||||||
|
request: {
|
||||||
|
identity: userInterfaces.IIdentity;
|
||||||
|
secretBundle: data.ISecretBundle;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
resultSecretBundle: data.ISecretBundle;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_DeleteSecretBundleById extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_DeleteSecretBundleById
|
||||||
|
> {
|
||||||
|
method: 'deleteSecretBundleById';
|
||||||
|
request: {
|
||||||
|
identity: userInterfaces.IIdentity;
|
||||||
|
secretBundleId: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
ok: boolean;
|
||||||
|
};
|
||||||
|
}
|
74
ts_interfaces/requests/secretgroup.ts
Normal file
74
ts_interfaces/requests/secretgroup.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
import * as data from '../data/index.js';
|
||||||
|
import * as userInterfaces from '../data/user.js';
|
||||||
|
|
||||||
|
export interface IReq_GetSecretGroups extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_GetSecretGroups
|
||||||
|
> {
|
||||||
|
method: 'getSecretGroups';
|
||||||
|
request: {
|
||||||
|
identity: userInterfaces.IIdentity;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
secretGroups: data.ISecretGroup[];
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_GetSecretGroupById extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_GetSecretGroupById
|
||||||
|
> {
|
||||||
|
method: 'getSecretGroupById';
|
||||||
|
request: {
|
||||||
|
identity: userInterfaces.IIdentity;
|
||||||
|
secretGroupId: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
secretGroup: data.ISecretGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_CreateSecretGroup extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_CreateSecretGroup
|
||||||
|
> {
|
||||||
|
method: 'createSecretGroup';
|
||||||
|
request: {
|
||||||
|
identity: userInterfaces.IIdentity;
|
||||||
|
secretGroup: data.ISecretGroup;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
resultSecretGroup: data.ISecretGroup;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_UpdateSecretGroup extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_UpdateSecretGroup
|
||||||
|
> {
|
||||||
|
method: 'updateSecretGroup';
|
||||||
|
request: {
|
||||||
|
identity: userInterfaces.IIdentity;
|
||||||
|
secretGroup: data.ISecretGroup;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
resultSecretGroup: data.ISecretGroup;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_DeleteSecretGroupById extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_DeleteSecretGroupById
|
||||||
|
> {
|
||||||
|
method: 'deleteSecretGroupById';
|
||||||
|
request: {
|
||||||
|
identity: userInterfaces.IIdentity;
|
||||||
|
secretGroupId: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
ok: boolean;
|
||||||
|
};
|
||||||
|
}
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/cloudly',
|
name: '@serve.zone/cloudly',
|
||||||
version: '4.5.2',
|
version: '4.5.3',
|
||||||
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.'
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ export const loginAction = loginStatePart.createAction<{ username: string; passw
|
|||||||
async (statePartArg, payloadArg) => {
|
async (statePartArg, payloadArg) => {
|
||||||
const currentState = statePartArg.getState();
|
const currentState = statePartArg.getState();
|
||||||
const trLogin =
|
const trLogin =
|
||||||
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>(
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.admin.IReq_Admin_LoginWithUsernameAndPassword>(
|
||||||
'/typedrequest',
|
'/typedrequest',
|
||||||
'adminLoginWithUsernameAndPassword'
|
'adminLoginWithUsernameAndPassword'
|
||||||
);
|
);
|
||||||
@ -77,20 +77,34 @@ export const dataState = await appstate.getStatePart<IDataState>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Getting data
|
// Getting data
|
||||||
export const getAllDataAction = dataState.createAction(async (statePartArg, partialArg?: 'secrets' | 'images') => {
|
export const getAllDataAction = dataState.createAction(async (statePartArg) => {
|
||||||
let currentState = statePartArg.getState();
|
let currentState = statePartArg.getState();
|
||||||
// Secrets
|
// SecretsGroups
|
||||||
const trGetSecrets =
|
const trGetSecretGroups =
|
||||||
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secretgroup.IReq_GetSecretGroups>(
|
||||||
'/typedrequest',
|
'/typedrequest',
|
||||||
'adminGetConfigBundlesAndSecretGroups'
|
'getSecretGroups'
|
||||||
);
|
);
|
||||||
const response = await trGetSecrets.fire({
|
const response = await trGetSecretGroups.fire({
|
||||||
identity: loginStatePart.getState().identity,
|
identity: loginStatePart.getState().identity,
|
||||||
});
|
});
|
||||||
currentState = {
|
currentState = {
|
||||||
...currentState,
|
...currentState,
|
||||||
...response,
|
secretGroups: response.secretGroups,
|
||||||
|
};
|
||||||
|
|
||||||
|
// SecretBundles
|
||||||
|
const trGetSecretBundles =
|
||||||
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secretbundle.IReq_GetSecretBundles>(
|
||||||
|
'/typedrequest',
|
||||||
|
'getSecretBundles'
|
||||||
|
);
|
||||||
|
const responseSecretBundles = await trGetSecretBundles.fire({
|
||||||
|
identity: loginStatePart.getState().identity,
|
||||||
|
});
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
secretBundles: responseSecretBundles.secretBundles,
|
||||||
};
|
};
|
||||||
|
|
||||||
// images
|
// images
|
||||||
@ -104,7 +118,7 @@ export const getAllDataAction = dataState.createAction(async (statePartArg, part
|
|||||||
});
|
});
|
||||||
currentState = {
|
currentState = {
|
||||||
...currentState,
|
...currentState,
|
||||||
...responseImages,
|
images: responseImages.images,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clusters
|
// Clusters
|
||||||
@ -119,7 +133,7 @@ export const getAllDataAction = dataState.createAction(async (statePartArg, part
|
|||||||
|
|
||||||
currentState = {
|
currentState = {
|
||||||
...currentState,
|
...currentState,
|
||||||
...responseClusters,
|
clusters: responseClusters.clusters,
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentState;
|
return currentState;
|
||||||
@ -130,9 +144,9 @@ export const createSecretGroupAction = dataState.createAction(
|
|||||||
async (statePartArg, payloadArg: plugins.interfaces.data.ISecretGroup) => {
|
async (statePartArg, payloadArg: plugins.interfaces.data.ISecretGroup) => {
|
||||||
let currentState = statePartArg.getState();
|
let currentState = statePartArg.getState();
|
||||||
const trCreateSecretGroup =
|
const trCreateSecretGroup =
|
||||||
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_CreateConfigBundlesAndSecretGroups>(
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_CreateSecretBundlesAndGroups>(
|
||||||
'/typedrequest',
|
'/typedrequest',
|
||||||
'adminCreateConfigBundlesAndSecretGroups'
|
'adminCreateSecretBundlesAndGroups'
|
||||||
);
|
);
|
||||||
const response = await trCreateSecretGroup.fire({
|
const response = await trCreateSecretGroup.fire({
|
||||||
identity: loginStatePart.getState().identity,
|
identity: loginStatePart.getState().identity,
|
||||||
@ -149,7 +163,7 @@ export const deleteSecretGroupAction = dataState.createAction(
|
|||||||
async (statePartArg, payloadArg: { secretGroupId: string }) => {
|
async (statePartArg, payloadArg: { secretGroupId: string }) => {
|
||||||
let currentState = statePartArg.getState();
|
let currentState = statePartArg.getState();
|
||||||
const trDeleteSecretGroup =
|
const trDeleteSecretGroup =
|
||||||
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_DeleteConfigBundlesAndSecretGroups>(
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_DeleteSecretBundlesAndGroups>(
|
||||||
'/typedrequest',
|
'/typedrequest',
|
||||||
'adminDeleteConfigBundlesAndSecretGroups'
|
'adminDeleteConfigBundlesAndSecretGroups'
|
||||||
);
|
);
|
||||||
@ -168,7 +182,7 @@ export const deleteSecretBundleAction = dataState.createAction(
|
|||||||
async (statePartArg, payloadArg: { configBundleId: string }) => {
|
async (statePartArg, payloadArg: { configBundleId: string }) => {
|
||||||
let currentState = statePartArg.getState();
|
let currentState = statePartArg.getState();
|
||||||
const trDeleteConfigBundle =
|
const trDeleteConfigBundle =
|
||||||
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_DeleteConfigBundlesAndSecretGroups>(
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_DeleteSecretBundlesAndGroups>(
|
||||||
'/typedrequest',
|
'/typedrequest',
|
||||||
'adminDeleteConfigBundlesAndSecretGroups'
|
'adminDeleteConfigBundlesAndSecretGroups'
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user