Skip to content

Commit 2c5d6f3

Browse files
authored
feat: add configmaps fluent interface implementation (#1295)
Signed-off-by: Nathan Klick <[email protected]>
1 parent 04b616e commit 2c5d6f3

18 files changed

+419
-70
lines changed

src/core/config/remote/remote_config_manager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {ErrorMessages} from '../../error_messages.js';
2525
import {CommonFlagsDataWrapper} from './common_flags_data_wrapper.js';
2626
import {type AnyObject} from '../../../types/aliases.js';
2727
import {NamespaceName} from '../../kube/namespace_name.js';
28+
import {ResourceNotFoundError} from '../../kube/errors/resource_operation_errors.js';
2829

2930
/**
3031
* Uses Kubernetes ConfigMaps to manage the remote configuration data by creating, loading, modifying,
@@ -261,7 +262,7 @@ export class RemoteConfigManager {
261262
try {
262263
return await this.k8.getNamespacedConfigMap(constants.SOLO_REMOTE_CONFIGMAP_NAME);
263264
} catch (error: any) {
264-
if (error.meta?.statusCode !== StatusCodes.NOT_FOUND) {
265+
if (!(error instanceof ResourceNotFoundError)) {
265266
throw new SoloError('Failed to read remote config from cluster', error);
266267
}
267268

src/core/kube/config_maps.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import {type NamespaceName} from './namespace_name.js';
66

77
export interface ConfigMaps {
88
/**
9-
* Create a new config map
9+
* Create a new config map. If the config map already exists, it will not be replaced.
10+
*
1011
* @param namespace - for the config map
1112
* @param name - for the config name
1213
* @param labels - for the config metadata
1314
* @param data - to contain in the config
15+
* @throws {ResourceCreateError} if the config map could not be created.
16+
* @throws {KubeApiError} if the API call fails for an unexpected reason.
1417
*/
1518
create(
1619
namespace: NamespaceName,
@@ -20,11 +23,22 @@ export interface ConfigMaps {
2023
): Promise<boolean>; // TODO was createNamespacedConfigMap
2124

2225
/**
23-
* Delete a config map
26+
* Create or replace a config map. If the config map already exists, it will be replaced.
27+
*
2428
* @param namespace - for the config map
2529
* @param name - for the config name
30+
* @param labels - for the config metadata
31+
* @param data - to contain in the config
32+
* @throws {ResourceCreateError} if the config map could not be created.
33+
* @throws {ResourceReplaceError} if the config map could not be replaced.
34+
* @throws {KubeApiError} if the API call fails for an unexpected reason.
2635
*/
27-
delete(namespace: NamespaceName, name: string): Promise<boolean>; // TODO was deleteNamespacedConfigMap
36+
createOrReplace(
37+
namespace: NamespaceName,
38+
name: string,
39+
labels: Record<string, string>,
40+
data: Record<string, string>,
41+
): Promise<boolean>;
2842

2943
/**
3044
* Read a config map
@@ -46,4 +60,18 @@ export interface ConfigMaps {
4660
labels: Record<string, string>,
4761
data: Record<string, string>,
4862
): Promise<boolean>; // TODO was replaceNamespacedConfigMap
63+
64+
/**
65+
* Delete a config map
66+
* @param namespace - for the config map
67+
* @param name - for the config name
68+
*/
69+
delete(namespace: NamespaceName, name: string): Promise<boolean>; // TODO was deleteNamespacedConfigMap
70+
71+
/**
72+
* Check if a config map exists
73+
* @param namespace - for the config map
74+
* @param name - for the config name
75+
*/
76+
exists(namespace: NamespaceName, name: string): Promise<boolean>;
4977
}

src/core/kube/container_name.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* SPDX-License-Identifier: Apache-2.0
33
*/
44
import {isDns1123Label} from './kube_validation.js';
5-
import {ContainerNameInvalidError} from './kube_errors.js';
5+
import {ContainerNameInvalidError} from './errors/namespace_name_invalid_error.js';
66

77
/**
88
* Represents a Kubernetes container name. A Kubernetes container name must be a valid RFC-1123 DNS label.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* SPDX-License-Identifier: Apache-2.0
3+
*/
4+
import {SoloError} from '../../errors.js';
5+
6+
export class KubeApiError extends SoloError {
7+
/**
8+
* Instantiates a new error with a message and an optional cause.
9+
*
10+
* @param message - the error message.
11+
* @param statusCode - the HTTP status code.
12+
* @param cause - optional underlying cause of the error.
13+
* @param meta - optional metadata to be reported.
14+
*/
15+
public constructor(message: string, statusCode: number, cause?: Error, meta?: object) {
16+
super(message + `, statusCode: ${statusCode}`, cause, {...meta, ...{statusCode: statusCode}});
17+
}
18+
}

src/core/kube/kube_errors.ts renamed to src/core/kube/errors/namespace_name_invalid_error.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* SPDX-License-Identifier: Apache-2.0
33
*/
4-
import {SoloError} from '../errors.js';
4+
import {SoloError} from '../../errors.js';
55

66
const RFC_1123_POSTFIX = (prefix: string) => `${prefix} is invalid, must be a valid RFC-1123 DNS label. \` +
77
"A DNS 1123 label must consist of lower case alphanumeric characters, '-' " +
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* SPDX-License-Identifier: Apache-2.0
3+
*/
4+
import {SoloError} from '../../errors.js';
5+
import {ResourceOperation} from '../resource_operation.js';
6+
import {type ResourceType} from '../resource_type.js';
7+
import {type NamespaceName} from '../namespace_name.js';
8+
9+
export class ResourceOperationError extends SoloError {
10+
/**
11+
* Instantiates a new error with a message and an optional cause.
12+
* @param operation - the operation that failed.
13+
* @param resourceType - the type of resource that failed to read.
14+
* @param namespace - the namespace of the resource.
15+
* @param name - the name of the resource.
16+
* @param cause - optional underlying cause of the error.
17+
*/
18+
public constructor(
19+
operation: ResourceOperation,
20+
resourceType: ResourceType,
21+
namespace: NamespaceName,
22+
name: string,
23+
cause?: Error,
24+
) {
25+
super(`failed to ${operation} ${resourceType} '${name}' in namespace '${namespace}'`, cause, {
26+
operation: operation,
27+
resourceType: resourceType,
28+
namespace: namespace?.name,
29+
name: name,
30+
});
31+
}
32+
}
33+
34+
export class ResourceReadError extends ResourceOperationError {
35+
/**
36+
* Instantiates a new error with a message and an optional cause.
37+
* @param resourceType - the type of resource that failed to read.
38+
* @param namespace - the namespace of the resource.
39+
* @param name - the name of the resource.
40+
* @param cause - optional underlying cause of the error.
41+
*/
42+
public constructor(resourceType: ResourceType, namespace: NamespaceName, name: string, cause?: Error) {
43+
super(ResourceOperation.READ, resourceType, namespace, name, cause);
44+
}
45+
}
46+
47+
export class ResourceCreateError extends ResourceOperationError {
48+
/**
49+
* Instantiates a new error with a message and an optional cause.
50+
* @param resourceType - the type of resource that failed to create.
51+
* @param namespace - the namespace of the resource.
52+
* @param name - the name of the resource.
53+
* @param cause - optional underlying cause of the error.
54+
*/
55+
public constructor(resourceType: ResourceType, namespace: NamespaceName, name: string, cause?: Error) {
56+
super(ResourceOperation.CREATE, resourceType, namespace, name, cause);
57+
}
58+
}
59+
60+
export class ResourceUpdateError extends ResourceOperationError {
61+
/**
62+
* Instantiates a new error with a message and an optional cause.
63+
* @param resourceType - the type of resource that failed to update.
64+
* @param namespace - the namespace of the resource.
65+
* @param name - the name of the resource.
66+
* @param cause - optional underlying cause of the error.
67+
*/
68+
public constructor(resourceType: ResourceType, namespace: NamespaceName, name: string, cause?: Error) {
69+
super(ResourceOperation.UPDATE, resourceType, namespace, name, cause);
70+
}
71+
}
72+
73+
export class ResourceDeleteError extends ResourceOperationError {
74+
/**
75+
* Instantiates a new error with a message and an optional cause.
76+
* @param resourceType - the type of resource that failed to delete.
77+
* @param namespace - the namespace of the resource.
78+
* @param name - the name of the resource.
79+
* @param cause - optional underlying cause of the error.
80+
*/
81+
public constructor(resourceType: ResourceType, namespace: NamespaceName, name: string, cause?: Error) {
82+
super(ResourceOperation.DELETE, resourceType, namespace, name, cause);
83+
}
84+
}
85+
86+
export class ResourceReplaceError extends ResourceOperationError {
87+
/**
88+
* Instantiates a new error with a message and an optional cause.
89+
* @param resourceType - the type of resource that failed to replace.
90+
* @param namespace - the namespace of the resource.
91+
* @param name - the name of the resource.
92+
* @param cause - optional underlying cause of the error.
93+
*/
94+
public constructor(resourceType: ResourceType, namespace: NamespaceName, name: string, cause?: Error) {
95+
super(ResourceOperation.REPLACE, resourceType, namespace, name, cause);
96+
}
97+
}
98+
99+
export class ResourceNotFoundError extends ResourceOperationError {
100+
/**
101+
* Instantiates a new error with a message and an optional cause.
102+
* @param operation - the operation that failed.
103+
* @param resourceType - the type of resource that failed to read.
104+
* @param namespace - the namespace of the resource.
105+
* @param name - the name of the resource.
106+
* @param cause - optional underlying cause of the error.
107+
*/
108+
public constructor(
109+
operation: ResourceOperation,
110+
resourceType: ResourceType,
111+
namespace: NamespaceName,
112+
name: string,
113+
cause?: Error,
114+
) {
115+
super(operation, resourceType, namespace, name, cause);
116+
}
117+
}

src/core/kube/k8.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {type V1Lease} from '@kubernetes/client-node';
1010
import {type Namespaces} from './namespaces.js';
1111
import {type NamespaceName} from './namespace_name.js';
1212
import {type Clusters} from './clusters.js';
13+
import {type ConfigMaps} from './config_maps.js';
1314
import {type ContainerName} from './container_name.js';
1415

1516
export interface K8 {
@@ -21,10 +22,16 @@ export interface K8 {
2122

2223
/**
2324
* Fluent accessor for reading and manipulating cluster information from the kubeconfig file.
24-
* returns an object instance providing cluster operations
25+
* @returns an object instance providing cluster operations
2526
*/
2627
clusters(): Clusters;
2728

29+
/**
30+
* Fluent accessor for reading and manipulating config maps.
31+
* @returns an object instance providing config map operations
32+
*/
33+
configMaps(): ConfigMaps;
34+
2835
/**
2936
* Create a new namespace
3037
* @param namespace - the namespace to create

src/core/kube/k8_client.ts

Lines changed: 17 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import {type Namespaces} from './namespaces.js';
3232
import {NamespaceName} from './namespace_name.js';
3333
import K8ClientClusters from './k8_client/k8_client_clusters.js';
3434
import {type Clusters} from './clusters.js';
35+
import {type ConfigMaps} from './config_maps.js';
36+
import K8ClientConfigMaps from './k8_client/k8_client_config_maps.js';
3537
import {PodRef} from './pod_ref.js';
3638
import {type ContainerName} from './container_name.js';
3739

@@ -57,6 +59,7 @@ export class K8Client implements K8 {
5759
private networkingApi: k8s.NetworkingV1Api;
5860

5961
private k8Clusters: K8ClientClusters;
62+
private k8ConfigMaps: K8ClientConfigMaps;
6063

6164
constructor(
6265
@inject(ConfigManager) private readonly configManager?: ConfigManager,
@@ -86,6 +89,7 @@ export class K8Client implements K8 {
8689
this.coordinationApiClient = this.kubeConfig.makeApiClient(k8s.CoordinationV1Api);
8790

8891
this.k8Clusters = new K8ClientClusters(this.kubeConfig);
92+
this.k8ConfigMaps = new K8ClientConfigMaps(this.kubeClient);
8993

9094
return this; // to enable chaining
9195
}
@@ -97,12 +101,20 @@ export class K8Client implements K8 {
97101

98102
/**
99103
* Fluent accessor for reading and manipulating cluster information from the kubeconfig file.
100-
* returns an object instance providing cluster operations
104+
* @returns an object instance providing cluster operations
101105
*/
102106
public clusters(): Clusters {
103107
return this.k8Clusters;
104108
}
105109

110+
/**
111+
* Fluent accessor for reading and manipulating config maps in the kubernetes cluster.
112+
* @returns an object instance providing config map operations
113+
*/
114+
public configMaps(): ConfigMaps {
115+
return this.k8ConfigMaps;
116+
}
117+
106118
/**
107119
* Apply filters to metadata
108120
* @param items - list of items
@@ -1148,80 +1160,27 @@ export class K8Client implements K8 {
11481160
/* ------------- ConfigMap ------------- */
11491161

11501162
public async getNamespacedConfigMap(name: string): Promise<k8s.V1ConfigMap> {
1151-
const {response, body} = await this.kubeClient
1152-
.readNamespacedConfigMap(name, this.getNamespace().name)
1153-
.catch(e => e);
1154-
1155-
this.handleKubernetesClientError(response, body, 'Failed to get namespaced configmap');
1156-
1157-
return body as k8s.V1ConfigMap;
1163+
return this.configMaps().read(this.getNamespace(), name);
11581164
}
11591165

11601166
public async createNamespacedConfigMap(
11611167
name: string,
11621168
labels: Record<string, string>,
11631169
data: Record<string, string>,
11641170
): Promise<boolean> {
1165-
const namespace = this.getNamespace();
1166-
1167-
const configMap = new k8s.V1ConfigMap();
1168-
configMap.data = data;
1169-
1170-
const metadata = new k8s.V1ObjectMeta();
1171-
metadata.name = name;
1172-
metadata.namespace = namespace.name;
1173-
metadata.labels = labels;
1174-
configMap.metadata = metadata;
1175-
try {
1176-
const resp = await this.kubeClient.createNamespacedConfigMap(namespace.name, configMap);
1177-
1178-
return resp.response.statusCode === StatusCodes.CREATED;
1179-
} catch (e) {
1180-
throw new SoloError(
1181-
`failed to create configmap ${name} in namespace ${namespace}: ${e.message}, ${e?.body?.message}`,
1182-
e,
1183-
);
1184-
}
1171+
return this.configMaps().create(this.getNamespace(), name, labels, data);
11851172
}
11861173

11871174
public async replaceNamespacedConfigMap(
11881175
name: string,
11891176
labels: Record<string, string>,
11901177
data: Record<string, string>,
11911178
): Promise<boolean> {
1192-
const namespace = this.getNamespace();
1193-
1194-
const configMap = new k8s.V1ConfigMap();
1195-
configMap.data = data;
1196-
1197-
const metadata = new k8s.V1ObjectMeta();
1198-
metadata.name = name;
1199-
metadata.namespace = namespace.name;
1200-
metadata.labels = labels;
1201-
configMap.metadata = metadata;
1202-
try {
1203-
const resp = await this.kubeClient.replaceNamespacedConfigMap(name, namespace.name, configMap);
1204-
1205-
return resp.response.statusCode === StatusCodes.CREATED;
1206-
} catch (e) {
1207-
throw new SoloError(
1208-
`failed to replace configmap ${name} in namespace ${namespace}: ${e.message}, ${e?.body?.message}`,
1209-
e,
1210-
);
1211-
}
1179+
return this.configMaps().replace(this.getNamespace(), name, labels, data);
12121180
}
12131181

12141182
public async deleteNamespacedConfigMap(name: string, namespace: NamespaceName): Promise<boolean> {
1215-
try {
1216-
const resp = await this.kubeClient.deleteNamespacedConfigMap(name, namespace.name);
1217-
1218-
return resp.response.statusCode === StatusCodes.CREATED;
1219-
} catch (e) {
1220-
throw new SoloError(
1221-
`failed to delete configmap ${name} in namespace ${namespace}: ${e.message}, ${e?.body?.message}`,
1222-
e,
1223-
);
1224-
}
1183+
return this.configMaps().delete(namespace, name);
12251184
}
12261185

12271186
// --------------------------------------- LEASES --------------------------------------- //

0 commit comments

Comments
 (0)