@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
165 lines (149 loc) • 5.59 kB
text/typescript
// SPDX-License-Identifier: Apache-2.0
import {type Secrets} from '../../../resources/secret/secrets.js';
import {type CoreV1Api, V1ObjectMeta, V1Secret, type V1SecretList} from '@kubernetes/client-node';
import {type NamespaceName} from '../../../../../types/namespace/namespace-name.js';
import {type Optional} from '../../../../../types/index.js';
import {ResourceNotFoundError} from '../../../errors/resource-operation-errors.js';
import {ResourceType} from '../../../resources/resource-type.js';
import {Duration} from '../../../../../core/time/duration.js';
import {type SecretType} from '../../../resources/secret/secret-type.js';
import {type Secret} from '../../../resources/secret/secret.js';
import {KubeApiResponse} from '../../../kube-api-response.js';
import {ResourceOperation} from '../../../resources/resource-operation.js';
export class K8ClientSecrets implements Secrets {
public constructor(private readonly kubeClient: CoreV1Api) {}
public async create(
namespace: NamespaceName,
name: string,
secretType: SecretType,
data: Record<string, string>,
labels: Optional<Record<string, string>>,
): Promise<boolean> {
return await this.createOrReplaceWithForce(namespace, name, secretType, data, labels, false, true);
}
public async createOrReplace(
namespace: NamespaceName,
name: string,
secretType: SecretType,
data: Record<string, string>,
labels: Optional<Record<string, string>>,
): Promise<boolean> {
return await this.createOrReplaceWithForce(namespace, name, secretType, data, labels, false, false);
}
public async delete(namespace: NamespaceName, name: string): Promise<boolean> {
try {
await this.kubeClient.deleteNamespacedSecret({name, namespace: namespace.name});
} catch (error) {
if (KubeApiResponse.isNotFound(error)) {
return true;
}
KubeApiResponse.throwError(error, ResourceOperation.DELETE, ResourceType.SECRET, namespace, name);
}
return true;
}
public async replace(
namespace: NamespaceName,
name: string,
secretType: SecretType,
data: Record<string, string>,
labels: Optional<Record<string, string>>,
): Promise<boolean> {
return this.createOrReplaceWithForce(namespace, name, secretType, data, labels, true);
}
public async read(namespace: NamespaceName, name: string): Promise<Secret> {
let result: V1Secret;
try {
result = await this.kubeClient.readNamespacedSecret({name, namespace: namespace.name});
} catch (error) {
KubeApiResponse.throwError(error, ResourceOperation.READ, ResourceType.SECRET, namespace, name);
}
return {
name: result.metadata!.name as string,
labels: result.metadata!.labels as Record<string, string>,
namespace: result.metadata!.namespace as string,
type: result.type as string,
data: result.data as Record<string, string>,
};
}
public async list(namespace: NamespaceName, labels?: string[]): Promise<Array<Secret>> {
const labelSelector: string = labels ? labels.join(',') : undefined;
let secretList: V1SecretList;
try {
secretList = await this.kubeClient.listNamespacedSecret({
namespace: namespace.toString(),
labelSelector,
timeoutSeconds: Duration.ofMinutes(5).toMillis(),
});
} catch (error) {
KubeApiResponse.throwError(error, ResourceOperation.LIST, ResourceType.SECRET, namespace, '');
}
return secretList.items.map((secret: V1Secret): Secret => {
return {
name: secret.metadata!.name as string,
labels: secret.metadata!.labels as Record<string, string>,
namespace: secret.metadata!.namespace as string,
type: secret.type as string,
data: secret.data as Record<string, string>,
};
});
}
public async exists(namespace: NamespaceName, name: string): Promise<boolean> {
try {
const cm: Secret = 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,
secretType: SecretType,
data: Record<string, string>,
labels: Optional<Record<string, string>>,
forceReplace?: boolean,
forceCreate?: boolean,
): Promise<boolean> {
const replace: boolean = await this.shouldReplace(namespace, name, forceReplace, forceCreate);
const v1Secret: V1Secret = new V1Secret();
v1Secret.apiVersion = 'v1';
v1Secret.kind = 'Secret';
v1Secret.type = secretType;
v1Secret.data = data;
v1Secret.metadata = new V1ObjectMeta();
v1Secret.metadata.name = name;
v1Secret.metadata.labels = labels;
try {
await (replace
? this.kubeClient.replaceNamespacedSecret({name, namespace: namespace.name, body: v1Secret})
: this.kubeClient.createNamespacedSecret({namespace: namespace.name, body: v1Secret}));
} catch (error) {
KubeApiResponse.throwError(
error,
replace ? ResourceOperation.REPLACE : ResourceOperation.CREATE,
ResourceType.SECRET,
namespace,
name,
);
}
return true;
}
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);
}
}