@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
225 lines (199 loc) • 11.6 kB
text/typescript
// SPDX-License-Identifier: Apache-2.0
import {type KindClient} from '../kind-client.js';
import {GetClustersRequest} from '../request/get/get-clusters-request.js';
import {KindCluster} from '../model/kind-cluster.js';
import {type KindRequest} from '../request/kind-request.js';
import {KindExecutionBuilder} from '../execution/kind-execution-builder.js';
import {type KindExecution} from '../execution/kind-execution.js';
import {VersionRequest} from '../request/version-request.js';
import {KindVersion} from '../model/kind-version.js';
import {ClusterCreateRequest} from '../request/cluster/cluster-create-request.js';
import {type ClusterCreateOptions} from '../model/create-cluster/cluster-create-options.js';
import {ClusterCreateOptionsBuilder} from '../model/create-cluster/create-cluster-options-builder.js';
import {ClusterCreateResponse} from '../model/create-cluster/cluster-create-response.js';
import {ClusterDeleteResponse} from '../model/delete-cluster/cluster-delete-response.js';
import {type ClusterDeleteOptions} from '../model/delete-cluster/cluster-delete-options.js';
import {ClusterDeleteOptionsBuilder} from '../model/delete-cluster/cluster-delete-options-builder.js';
import {ClusterDeleteRequest} from '../request/cluster/cluster-delete-request.js';
import {BuildNodeImagesResponse} from '../model/build-node-images/build-node-images-response.js';
import {type BuildNodeImagesOptions} from '../model/build-node-images/build-node-images-options.js';
import {BuildNodeImagesRequest} from '../request/build/build-node-images-request.js';
import {ExportLogsRequest} from '../request/export/export-logs-request.js';
import {type ExportLogsOptions} from '../model/export-logs/export-logs-options.js';
import {ExportLogsResponse} from '../model/export-logs/export-logs-response.js';
import {ExportLogsOptionsBuilder} from '../model/export-logs/export-logs-options-builder.js';
import {ExportKubeConfigOptionsBuilder} from '../model/export-kubeconfig/export-kubeconfig-options-builder.js';
import {type ExportKubeConfigOptions} from '../model/export-kubeconfig/export-kubeconfig-options.js';
import {ExportKubeConfigRequest} from '../request/export/export-kubeconfig-request.js';
import {ExportKubeConfigResponse} from '../model/export-kubeconfig/export-kubeconfig-response.js';
import {GetNodesResponse} from '../model/get-nodes/get-nodes-response.js';
import {type GetNodesOptions} from '../model/get-nodes/get-nodes-options.js';
import {GetNodesOptionsBuilder} from '../model/get-nodes/get-nodes-options-builder.js';
import {GetNodesRequest} from '../request/get/get-nodes-request.js';
import {GetKubeConfigOptionsBuilder} from '../model/get-kubeconfig/get-kubeconfig-options-builder.js';
import {type GetKubeConfigOptions} from '../model/get-kubeconfig/get-kubeconfig-options.js';
import {GetKubeConfigRequest} from '../request/get/get-kubeconfig-request.js';
import {GetKubeConfigResponse} from '../model/get-kubeconfig/get-kubeconfig-response.js';
import {type LoadDockerImageOptions} from '../model/load-docker-image/load-docker-image-options.js';
import {LoadDockerImageRequest} from '../request/load/docker-image-request.js';
import {LoadDockerImageResponse} from '../model/load-docker-image/load-docker-image-response.js';
import {LoadDockerImageOptionsBuilder} from '../model/load-docker-image/load-docker-image-options-builder.js';
import {type LoadImageArchiveOptions} from '../model/load-image-archive/load-image-archive-options.js';
import {LoadImageArchiveOptionsBuilder} from '../model/load-image-archive/load-image-archive-options-builder.js';
import {LoadImageArchiveResponse} from '../model/load-image-archive/load-image-archive-response.js';
import {LoadImageArchiveRequest} from '../request/load/image-archive-request.js';
import {KIND_VERSION} from '../../../../version.js';
import {KindVersionRequirementException} from '../errors/kind-version-requirement-exception.js';
import {inject} from 'tsyringe-neo';
import {InjectTokens} from '../../../core/dependency-injection/inject-tokens.js';
import {type SoloLogger} from '../../../core/logging/solo-logger.js';
import {patchInject} from '../../../core/dependency-injection/container-helper.js';
import path from 'node:path';
import {SemanticVersion} from '../../../business/utils/semantic-version.js';
type BiFunction<T, U, R> = (t: T, u: U) => R;
export class DefaultKindClient implements KindClient {
private static minimumVersion: SemanticVersion<string> = new SemanticVersion<string>(KIND_VERSION);
public constructor(
private readonly executable: string,
(InjectTokens.SoloLogger) private readonly logger?: SoloLogger,
(InjectTokens.KindInstallationDirectory) private readonly installationDirectory?: string,
) {
if (!executable || !executable.trim()) {
throw new Error('executable must not be blank');
}
this.executable = executable;
this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name);
this.installationDirectory = patchInject(
installationDirectory,
InjectTokens.KindInstallationDirectory,
this.constructor.name,
);
}
public async checkVersion(): Promise<void> {
const version: SemanticVersion<string> = await this.version();
if (version.lessThan(DefaultKindClient.minimumVersion)) {
throw new KindVersionRequirementException(
`The Kind CLI version ${version} is lower than the minimum required version ${DefaultKindClient.minimumVersion}.`,
);
}
}
public async version(): Promise<SemanticVersion<string>> {
const request: VersionRequest = new VersionRequest();
const builder: KindExecutionBuilder = new KindExecutionBuilder();
builder.executable(this.executable);
request.apply(builder);
const execution: KindExecution = builder.build();
if (execution instanceof Promise) {
throw new TypeError('Unexpected async execution');
}
const versionClass: typeof KindVersion = KindVersion;
const result: KindVersion = await execution.responseAs(versionClass);
if (!(result instanceof KindVersion)) {
throw new TypeError('Unexpected response type');
}
const semver: SemanticVersion<string> = result.getVersion();
this.logger?.info?.(`kind version: ${semver.toString()}`);
return semver;
}
public async createCluster(clusterName: string, options?: ClusterCreateOptions): Promise<ClusterCreateResponse> {
const builder: ClusterCreateOptionsBuilder = ClusterCreateOptionsBuilder.from(options);
builder.name(clusterName);
return this.executeAsync(new ClusterCreateRequest(builder.build()), ClusterCreateResponse);
}
public async deleteCluster(clusterName?: string, options?: ClusterDeleteOptions): Promise<ClusterDeleteResponse> {
const builder: ClusterDeleteOptionsBuilder = ClusterDeleteOptionsBuilder.from(options);
builder.name(clusterName);
return this.executeAsync(new ClusterDeleteRequest(builder.build()), ClusterDeleteResponse);
}
public async buildNodeImage(options?: BuildNodeImagesOptions): Promise<BuildNodeImagesResponse> {
return this.executeAsync(new BuildNodeImagesRequest(options), BuildNodeImagesResponse);
}
public async exportLogs(clusterName?: string): Promise<ExportLogsResponse> {
const options: ExportLogsOptions = ExportLogsOptionsBuilder.builder().name(clusterName).build();
return this.executeAsync(new ExportLogsRequest(options), ExportLogsResponse);
}
public async exportKubeConfig(clusterName?: string): Promise<ExportKubeConfigResponse> {
const options: ExportKubeConfigOptions = ExportKubeConfigOptionsBuilder.builder().name(clusterName).build();
return this.executeAsync(new ExportKubeConfigRequest(options), ExportKubeConfigResponse);
}
public async getClusters(): Promise<KindCluster[]> {
return this.executeAsList(new GetClustersRequest(), KindCluster);
}
public async getNodes(contextName?: string, options?: GetNodesOptions): Promise<GetNodesResponse> {
const builder: GetNodesOptionsBuilder = GetNodesOptionsBuilder.from(options).name(contextName);
return this.executeAsync(new GetNodesRequest(builder.build()), GetNodesResponse);
}
public async getKubeConfig(contextName?: string, options?: GetKubeConfigOptions): Promise<GetKubeConfigResponse> {
const builder: GetKubeConfigOptionsBuilder = GetKubeConfigOptionsBuilder.from(options).name(contextName);
return this.executeAsync(new GetKubeConfigRequest(builder.build()), GetKubeConfigResponse);
}
public async loadDockerImage(imageName: string, options?: LoadDockerImageOptions): Promise<LoadDockerImageResponse> {
const builder: LoadDockerImageOptionsBuilder = LoadDockerImageOptionsBuilder.from(options).imageName(imageName);
return this.executeAsync(new LoadDockerImageRequest(builder.build()), LoadDockerImageResponse);
}
public async loadImageArchive(
archivePath: string,
options?: LoadImageArchiveOptions,
): Promise<LoadImageArchiveResponse> {
const builder: LoadImageArchiveOptionsBuilder =
LoadImageArchiveOptionsBuilder.from(options).archivePath(archivePath);
await this.executeCall(new LoadImageArchiveRequest(builder.build()));
return new LoadImageArchiveResponse();
}
private async executeCall<T extends KindRequest>(request: T): Promise<void> {
const builder: KindExecutionBuilder = new KindExecutionBuilder();
builder.executable(this.executable);
builder.environmentVariable('PATH', `${this.installationDirectory}${path.delimiter}${process.env.PATH}`);
request.apply(builder);
const execution: KindExecution = builder.build();
await execution.call();
}
/**
* Executes the given request and returns the response as the given class.
* The request is executed using the default namespace.
*
* @param request - The request to execute
* @param responseClass - The class of the response
* @returns The response
*/
private async executeAsync<T extends KindRequest, R>(
request: T,
responseClass?: new (...arguments_: any[]) => R,
): Promise<R> {
return this.executeInternal(undefined, request, responseClass, async (b): Promise<R> => {
return await b.responseAs(responseClass);
});
}
/**
* Executes the given request and returns the response as a list of the given class.
* The request is executed using the default namespace.
*
* @param request - The request to execute
* @param responseClass - The class of the response
* @returns A list of response objects
*/
private async executeAsList<T extends KindRequest, R>(
request: T,
responseClass: new (...arguments_: any[]) => R,
): Promise<R[]> {
return this.executeInternal(undefined, request, responseClass, async (b): Promise<R[]> => {
return await b.responseAsList(responseClass);
});
}
private async executeInternal<T extends KindRequest, R, V>(
namespace: string | undefined,
request: T,
responseClass: new (...arguments_: any[]) => R,
responseFunction: BiFunction<KindExecution, typeof responseClass, Promise<V>>,
): Promise<V> {
if (namespace && !namespace.trim()) {
throw new Error('namespace must not be blank');
}
const builder: KindExecutionBuilder = new KindExecutionBuilder();
builder.executable(this.executable);
builder.environmentVariable('PATH', `${this.installationDirectory}${path.delimiter}${process.env.PATH}`);
request.apply(builder);
const execution: KindExecution = builder.build();
return responseFunction(execution, responseClass);
}
}