Skip to content

Commit

Permalink
refactor(api-server): instantiate plugins in api-server without insta…
Browse files Browse the repository at this point in the history
…ll them

Added parameter action to PluginImport to determine if the plugin
should be installed.

Enabled feature to instantiate plugins from cactus-cmd-api-server
without install it.

Enabled feature to install packages from github

Added method create in class ConfigService to override old configuration.
Method newExampleConfigConvict accepts a boolean parameter to force
configuration override.

Closes hyperledger-cacti#1210

Signed-off-by: Elena Izaguirre <e.izaguirre.equiza@accenture.com>
  • Loading branch information
elenaizaguirre committed Nov 5, 2021
1 parent 9022064 commit 12afee9
Show file tree
Hide file tree
Showing 21 changed files with 526 additions and 93 deletions.
15 changes: 9 additions & 6 deletions packages/cactus-cmd-api-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ if (require.main === module) {
### Remote Plugin Imports at Runtime Example

```typescript
import { PluginImportType } from "@hyperledger/cactus-core-api";
import { PluginImportType, PluginImportAction } from "@hyperledger/cactus-core-api";
import { ApiServer } from "@hyperledger/cactus-cmd-api-server";
import { ConfigService } from "@hyperledger/cactus-cmd-api-server";
import { Logger, LoggerProvider } from "@hyperledger/cactus-common";
Expand Down Expand Up @@ -131,6 +131,9 @@ const main = async () => {
// being imported by the API server regardless of the language the plugin
// was written in.
type: PluginImportType.REMOTE,
// The INSTALL value means that the plugin will be installed instead of
// only instantiate it
action: PluginImportAction.INSTALL,
// The options that will be passed in to the plugin factory
options: {
keychainId: "_keychainId_",
Expand Down Expand Up @@ -250,7 +253,7 @@ Once you've built the container, the following commands should work:
--publish 4000:4000 \
cas \
./node_modules/.bin/cactusapi \
--plugins='[{"packageName": "@hyperledger/cactus-plugin-ledger-connector-fabric", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "options": { "connectionProfile": {}, "instanceId": "some-unique-instance-id"}}]'
--plugins='[{"packageName": "@hyperledger/cactus-plugin-ledger-connector-fabric", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "action": "org.hyperledger.cactus.plugin_import_action.INSTALL", "options": { "connectionProfile": {}, "instanceId": "some-unique-instance-id"}}]'
```

- Launch container with plugin configuration as an **environment variable**:
Expand All @@ -259,7 +262,7 @@ Once you've built the container, the following commands should work:
--rm \
--publish 3000:3000 \
--publish 4000:4000 \
--env PLUGINS='[{"packageName": "@hyperledger/cactus-plugin-ledger-connector-besu", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "options": {"rpcApiHttpHost": "http://localhost:8545", "instanceId": "some-unique-besu-connector-instance-id"}}]' \
--env PLUGINS='[{"packageName": "@hyperledger/cactus-plugin-ledger-connector-besu", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "action": "org.hyperledger.cactus.plugin_import_action.INSTALL", "options": {"rpcApiHttpHost": "http://localhost:8545", "instanceId": "some-unique-besu-connector-instance-id"}}]' \
cas
```

Expand All @@ -271,13 +274,13 @@ Once you've built the container, the following commands should work:
--publish 4000:4000 \
cas \
./node_modules/.bin/cactusapi \
--plugins='[{"packageName": "@hyperledger/cactus-plugin-ledger-connector-besu", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "options": {"rpcApiHttpHost": "http://localhost:8545", "instanceId": "some-unique-besu-connector-instance-id"}}]'
--plugins='[{"packageName": "@hyperledger/cactus-plugin-ledger-connector-besu", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "action": "org.hyperledger.cactus.plugin_import_action.INSTALL", "options": {"rpcApiHttpHost": "http://localhost:8545", "instanceId": "some-unique-besu-connector-instance-id"}}]'
```

- Launch container with **configuration file** mounted from host machine:
```sh

echo '[{"packageName": "@hyperledger/cactus-plugin-ledger-connector-besu", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "options": {"rpcApiHttpHost": "http://localhost:8545", "instanceId": "some-unique-besu-connector-instance-id"}}]' > cactus.json
echo '[{"packageName": "@hyperledger/cactus-plugin-ledger-connector-besu", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "action": "org.hyperledger.cactus.plugin_import_action.INSTALL", "options": {"rpcApiHttpHost": "http://localhost:8545", "instanceId": "some-unique-besu-connector-instance-id"}}]' > cactus.json

docker run \
--rm \
Expand Down Expand Up @@ -305,7 +308,7 @@ Don't have a Besu network on hand to test with? Test or develop against our Besu
--rm \
--publish 3000:3000 \
--publish 4000:4000 \
--env PLUGINS='[{"packageName": "@hyperledger/cactus-plugin-ledger-connector-besu", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "options": {"rpcApiHttpHost": "http://localhost:8545", "instanceId": "some-unique-besu-connector-instance-id"}}]' \
--env PLUGINS='[{"packageName": "@hyperledger/cactus-plugin-ledger-connector-besu", "type": "org.hyperledger.cactus.plugin_import_type.LOCAL", "action": "org.hyperledger.cactus.plugin_import_action.INSTALL", "options": {"rpcApiHttpHost": "http://localhost:8545", "instanceId": "some-unique-besu-connector-instance-id"}}]' \
cas
```

Expand Down
107 changes: 60 additions & 47 deletions packages/cactus-cmd-api-server/src/main/typescript/api-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
PluginFactoryFactory,
PluginImport,
Constants,
PluginImportAction,
} from "@hyperledger/cactus-core-api";

import { PluginRegistry } from "@hyperledger/cactus-core";
Expand Down Expand Up @@ -274,19 +275,26 @@ export class ApiServer {
}

public async getOrInitPluginRegistry(): Promise<PluginRegistry> {
if (!this.pluginRegistry) {
if (!this.options.pluginRegistry) {
this.log.info(`getOrInitPluginRegistry() initializing a new one...`);
this.pluginRegistry = await this.initPluginRegistry();
} else {
this.log.info(`getOrInitPluginRegistry() re-using injected one...`);
this.pluginRegistry = this.options.pluginRegistry;
try {
if (!this.pluginRegistry) {
if (!this.options.pluginRegistry) {
this.log.info(`getOrInitPluginRegistry() initializing a new one...`);
this.pluginRegistry = await this.initPluginRegistry();
} else {
this.log.info(`getOrInitPluginRegistry() re-using injected one...`);
this.pluginRegistry = this.options.pluginRegistry;
}
}
await this.prometheusExporter.setTotalPluginImports(
await this.getPluginImportsCount(),
);
return this.pluginRegistry;
} catch (e) {
this.pluginRegistry = new PluginRegistry({ plugins: [] });
const errorMessage = `Failed init PluginRegistry: ${e.stack}`;
this.log.error(errorMessage);
throw new Error(errorMessage);
}
await this.prometheusExporter.setTotalPluginImports(
await this.getPluginImportsCount(),
);
return this.pluginRegistry;
}

public async initPluginRegistry(): Promise<PluginRegistry> {
Expand All @@ -306,43 +314,44 @@ export class ApiServer {
pluginImport: PluginImport,
registry: PluginRegistry,
): Promise<ICactusPlugin> {
const fnTag = `${this.className}#instantiatePlugin`;
const { logLevel } = this.options.config;
const { packageName, options } = pluginImport;
this.log.info(`Creating plugin from package: ${packageName}`, options);
const pluginOptions = { ...options, logLevel, pluginRegistry: registry };

await this.installPluginPackage(pluginImport);

const packagePath = path.join(
this.pluginsPath,
options.instanceId,
"node_modules",
packageName,
);
this.log.debug("Package path: %o", packagePath);

// eslint-disable-next-line @typescript-eslint/no-var-requires
const pluginPackage = require(/* webpackIgnore: true */ packagePath);
const createPluginFactory = pluginPackage.createPluginFactory as PluginFactoryFactory;

const pluginFactoryOptions: IPluginFactoryOptions = {
pluginImportType: pluginImport.type,
};

const pluginFactory = await createPluginFactory(pluginFactoryOptions);

const plugin = await pluginFactory.create(pluginOptions);

// need to invoke the i-cactus-plugin onPluginInit functionality here before plugin registry can be used further
try {
await plugin.onPluginInit();
} catch (error) {
const fnTag = `${this.className}#instantiatePlugin`;
const packageName = plugin.getPackageName();
const instanceId = plugin.getInstanceId();
if (pluginImport.action == PluginImportAction.Install) {
await this.installPluginPackage(pluginImport);
} else {
this.log.info(
`The installation of the plugin package ${packageName} was skipped due to the configuration flag action`,
);
}

const errorMessage = `${fnTag} failed calling onPluginInit() on the plugin '${packageName}' with the instanceId '${instanceId}'`;
const packagePath = path.join(
this.pluginsPath,
options.instanceId,
"node_modules",
packageName,
);
this.log.debug("Package path: %o", packagePath);

// eslint-disable-next-line @typescript-eslint/no-var-requires
const pluginPackage = require(/* webpackIgnore: true */ packagePath);
const createPluginFactory = pluginPackage.createPluginFactory as PluginFactoryFactory;
const pluginFactoryOptions: IPluginFactoryOptions = {
pluginImportType: pluginImport.type,
};
const pluginFactory = await createPluginFactory(pluginFactoryOptions);
const plugin = await pluginFactory.create(pluginOptions);

// need to invoke the i-cactus-plugin onPluginInit functionality here before plugin registry can be used further
await plugin.onPluginInit();

return plugin;
} catch (error) {
const errorMessage = `${fnTag} failed instantiating plugin '${packageName}' with the instanceId '${options.instanceId}'`;
this.log.error(errorMessage, error);

if (error instanceof Error) {
Expand All @@ -351,15 +360,15 @@ export class ApiServer {
throw new RuntimeError(errorMessage, JSON.stringify(error));
}
}

return plugin;
}

private async installPluginPackage(
pluginImport: PluginImport,
): Promise<void> {
const fnTag = `ApiServer#installPluginPackage()`;
const { packageName: pkgName } = pluginImport;
const pkgName = pluginImport.options.packageSrc
? pluginImport.options.packageSrc
: pluginImport.packageName;

const instanceId = pluginImport.options.instanceId;
const pluginPackageDir = path.join(this.pluginsPath, instanceId);
Expand Down Expand Up @@ -396,7 +405,14 @@ export class ApiServer {
}
this.log.info(`Installed ${pkgName} OK`);
} catch (ex) {
throw new RuntimeError(`${fnTag} plugin install fail: ${pkgName}`, ex);
const errorMessage = `${fnTag} failed installing plugin '${pkgName}`;
this.log.error(errorMessage, ex);

if (ex instanceof Error) {
throw new RuntimeError(errorMessage, ex);
} else {
throw new RuntimeError(errorMessage, JSON.stringify(ex));
}
}
}

Expand Down Expand Up @@ -648,9 +664,6 @@ export class ApiServer {
this.log.info(`Authorization request handler configured OK.`);
}

// const openApiValidator = this.createOpenApiValidator();
// await openApiValidator.install(app);

this.getOrCreateWebServices(app); // The API server's own endpoints

this.log.info(`Starting to install web services...`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
Constants,
PluginImport,
PluginImportType,
PluginImportAction,
} from "@hyperledger/cactus-core-api";

import { FORMAT_PLUGIN_ARRAY } from "./convict-plugin-array-format";
Expand Down Expand Up @@ -460,10 +461,12 @@ export class ConfigService {

public newExampleConfigConvict(
cactusApiServerOptions?: ICactusApiServerOptions,
overrides?: boolean,
): Config<ICactusApiServerOptions> {
cactusApiServerOptions = cactusApiServerOptions || this.newExampleConfig();
const env = this.newExampleConfigEnv(cactusApiServerOptions);
return this.getOrCreate({ env });
const conf = overrides ? this.create({ env }) : this.getOrCreate({ env });
return conf;
}

public newExampleConfig(): ICactusApiServerOptions {
Expand Down Expand Up @@ -524,6 +527,7 @@ export class ConfigService {
{
packageName: "@hyperledger/cactus-plugin-keychain-memory",
type: PluginImportType.Local,
action: PluginImportAction.Install,
options: {
instanceId: uuidV4(),
keychainId: uuidV4(),
Expand All @@ -532,6 +536,7 @@ export class ConfigService {
{
packageName: "@hyperledger/cactus-plugin-consortium-manual",
type: PluginImportType.Local,
action: PluginImportAction.Install,
options: {
instanceId: uuidV4(),
keyPairPem,
Expand Down Expand Up @@ -601,26 +606,34 @@ export class ConfigService {
args?: string[];
}): Config<ICactusApiServerOptions> {
if (!ConfigService.config) {
const schema: Schema<ICactusApiServerOptions> = ConfigService.getConfigSchema();
ConfigService.config = (convict as any)(schema, options);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
if (ConfigService.config.get("configFile")) {
const configFilePath = ConfigService.config.get("configFile");
ConfigService.config.loadFile(configFilePath);
}
ConfigService.config.validate();
this.validateKeyPairMatch();
const level = ConfigService.config.get("logLevel");
const logger: Logger = LoggerProvider.getOrCreate({
label: "config-service",
level,
});
logger.info("Configuration validation OK.");
this.create(options);
}
return ConfigService.config;
}

create(options?: {
env?: NodeJS.ProcessEnv;
args?: string[];
}): Config<ICactusApiServerOptions> {
const schema: Schema<ICactusApiServerOptions> = ConfigService.getConfigSchema();
ConfigService.config = (convict as any)(schema, options);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
if (ConfigService.config.get("configFile")) {
const configFilePath = ConfigService.config.get("configFile");
ConfigService.config.loadFile(configFilePath);
}
ConfigService.config.validate();
this.validateKeyPairMatch();
const level = ConfigService.config.get("logLevel");
const logger: Logger = LoggerProvider.getOrCreate({
label: "config-service",
level,
});
logger.info("Configuration validation OK.");
return ConfigService.config;
}

/**
* Validation that prevents operators from mistakenly deploying a key pair
* that they may not be operational for whatever reason.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { LogLevelDesc, LoggerProvider } from "@hyperledger/cactus-common";
import {
PluginImportType,
ConsortiumDatabase,
PluginImportAction,
} from "@hyperledger/cactus-core-api";

import {
Expand Down Expand Up @@ -71,6 +72,7 @@ test("Start API server, and run Artillery benchmark test.", async (t: Test) => {
{
packageName: "@hyperledger/cactus-plugin-keychain-memory",
type: PluginImportType.Local,
action: PluginImportAction.Install,
options: {
instanceId: uuidv4(),
keychainId: uuidv4(),
Expand All @@ -80,6 +82,7 @@ test("Start API server, and run Artillery benchmark test.", async (t: Test) => {
{
packageName: "@hyperledger/cactus-plugin-consortium-manual",
type: PluginImportType.Local,
action: PluginImportAction.Install,
options: {
instanceId: uuidv4(),
keyPairPem: keyPairPem,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { LoggerProvider, LogLevelDesc } from "@hyperledger/cactus-common";
import {
Configuration,
ConsortiumDatabase,
PluginImportAction,
PluginImportType,
} from "@hyperledger/cactus-core-api";
import { AuthorizationProtocol } from "../../../main/typescript/config/authorization-protocol";
Expand Down Expand Up @@ -88,6 +89,7 @@ test(testCase, async (t: Test) => {
{
packageName: "@hyperledger/cactus-plugin-keychain-memory",
type: PluginImportType.Local,
action: PluginImportAction.Install,
options: {
instanceId: uuidv4(),
keychainId: uuidv4(),
Expand All @@ -97,6 +99,7 @@ test(testCase, async (t: Test) => {
{
packageName: "@hyperledger/cactus-plugin-consortium-manual",
type: PluginImportType.Local,
action: PluginImportAction.Install,
options: {
instanceId: uuidv4(),
keyPairPem: keyPairPem,
Expand Down
Loading

0 comments on commit 12afee9

Please sign in to comment.