@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
204 lines (179 loc) • 6.7 kB
text/typescript
// SPDX-License-Identifier: Apache-2.0
import {
type CoreV1Api,
PatchStrategy,
setHeaderOptions,
V1ConfigMap,
type V1ConfigMapList,
V1ObjectMeta,
} from '@kubernetes/client-node';
import {type ConfigMaps} from '../../../resources/config-map/config-maps.js';
import {type NamespaceName} from '../../../../../types/namespace/namespace-name.js';
import {ResourceNotFoundError} from '../../../errors/resource-operation-errors.js';
import {ResourceType} from '../../../resources/resource-type.js';
import {ResourceOperation} from '../../../resources/resource-operation.js';
import {SoloError} from '../../../../../core/errors/solo-error.js';
import {type SoloLogger} from '../../../../../core/logging/solo-logger.js';
import {container} from 'tsyringe-neo';
import {type ConfigMap} from '../../../resources/config-map/config-map.js';
import {K8ClientConfigMap} from './k8-client-config-map.js';
import {InjectTokens} from '../../../../../core/dependency-injection/inject-tokens.js';
import {KubeApiResponse} from '../../../kube-api-response.js';
export class K8ClientConfigMaps implements ConfigMaps {
private readonly logger: SoloLogger;
public constructor(private readonly kubeClient: CoreV1Api) {
this.logger = container.resolve(InjectTokens.SoloLogger);
}
public async create(
namespace: NamespaceName,
name: string,
labels: Record<string, string>,
data: Record<string, string>,
): Promise<boolean> {
return await this.createOrReplaceWithForce(namespace, name, labels, data, false, true);
}
public async createOrReplace(
namespace: NamespaceName,
name: string,
labels: Record<string, string>,
data: Record<string, string>,
): Promise<boolean> {
return await this.createOrReplaceWithForce(namespace, name, labels, data, false, false);
}
public async delete(namespace: NamespaceName, name: string): Promise<boolean> {
try {
await this.kubeClient.deleteNamespacedConfigMap({
name,
namespace: namespace.name,
});
return true;
} catch (error) {
return KubeApiResponse.isFailingStatus(error);
}
}
public async read(namespace: NamespaceName, name: string): Promise<ConfigMap> {
try {
const body: V1ConfigMap = await this.kubeClient.readNamespacedConfigMap({name, namespace: namespace?.name});
return K8ClientConfigMap.fromV1ConfigMap(body);
} catch (error) {
KubeApiResponse.throwError(error, ResourceOperation.READ, ResourceType.CONFIG_MAP, namespace, name);
}
}
public async replace(
namespace: NamespaceName,
name: string,
labels: Record<string, string>,
data: Record<string, string>,
): Promise<boolean> {
return await this.createOrReplaceWithForce(namespace, name, labels, data, true, false);
}
public async exists(namespace: NamespaceName, name: string): Promise<boolean> {
try {
const cm: ConfigMap = await this.read(namespace, name);
return !!cm;
} catch (error) {
if (error instanceof ResourceNotFoundError) {
return false;
} else {
throw error;
}
}
}
private async createOrReplaceWithForce(
namespace: NamespaceName,
name: string,
labels: Record<string, string>,
data: Record<string, string>,
forceReplace?: boolean,
forceCreate?: boolean,
): Promise<boolean> {
const replace: boolean = await this.shouldReplace(namespace, name, forceReplace, forceCreate);
const configMap: V1ConfigMap = new V1ConfigMap();
configMap.data = data;
const metadata: V1ObjectMeta = new V1ObjectMeta();
metadata.name = name;
metadata.namespace = namespace.name;
metadata.labels = labels;
configMap.metadata = metadata;
try {
await (replace
? this.kubeClient.replaceNamespacedConfigMap({name, namespace: namespace.name, body: configMap})
: this.kubeClient.createNamespacedConfigMap({namespace: namespace.name, body: configMap}));
return true;
} catch (error) {
KubeApiResponse.throwError(
error,
replace ? ResourceOperation.REPLACE : ResourceOperation.CREATE,
ResourceType.CONFIG_MAP,
namespace,
name,
);
}
}
private async shouldReplace(
namespace: NamespaceName,
name: string,
forceReplace?: boolean,
forceCreate?: boolean,
): Promise<boolean> {
if (forceReplace && !forceCreate) {
return true;
}
if (forceCreate) {
return false;
}
return await this.exists(namespace, name);
}
public async list(namespace: NamespaceName, labels: string[]): Promise<ConfigMap[]> {
const labelSelector: string = labels ? labels.join(',') : undefined;
let results: V1ConfigMapList;
try {
results = await this.kubeClient.listNamespacedConfigMap({
namespace: namespace.name,
labelSelector,
});
} catch (error) {
KubeApiResponse.throwError(error, ResourceOperation.LIST, ResourceType.CONFIG_MAP, namespace, '');
}
return results?.items?.map((v1ConfigMap): ConfigMap => K8ClientConfigMap.fromV1ConfigMap(v1ConfigMap)) || [];
}
public async listForAllNamespaces(labels: string[]): Promise<ConfigMap[]> {
const labelSelector: string = labels ? labels.join(',') : undefined;
let results: V1ConfigMapList;
try {
results = await this.kubeClient.listConfigMapForAllNamespaces({labelSelector});
} catch (error) {
KubeApiResponse.throwError(error, ResourceOperation.LIST, ResourceType.CONFIG_MAP, undefined, '');
}
return results?.items?.map((v1ConfigMap): ConfigMap => K8ClientConfigMap.fromV1ConfigMap(v1ConfigMap)) || [];
}
public async update(namespace: NamespaceName, name: string, data: Record<string, string>): Promise<void> {
if (!(await this.exists(namespace, name))) {
throw new ResourceNotFoundError(ResourceOperation.READ, ResourceType.CONFIG_MAP, namespace, name);
}
const patch: {data: Record<string, string>} = {
data: data,
};
let result: V1ConfigMap;
try {
result = await this.kubeClient.patchNamespacedConfigMap(
{
name,
namespace: namespace.name,
body: patch,
},
setHeaderOptions('Content-Type', PatchStrategy.MergePatch),
);
this.logger.info(`Patched ConfigMap ${name} in namespace ${namespace}`);
} catch (error) {
KubeApiResponse.throwError(error, ResourceOperation.UPDATE, ResourceType.CONFIG_MAP, namespace, name);
}
if (result) {
return;
} else {
throw new SoloError(
`Failed to patch ConfigMap ${name} in namespace ${namespace}, no config map returned from patch`,
);
}
}
}