Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add contexts fluent interface implementation #1297

Merged
merged 1 commit into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/core/kube/contexts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* SPDX-License-Identifier: Apache-2.0
*/
import {type NamespaceName} from './namespace_name.js';

/**
* SPDX-License-Identifier: Apache-2.0
*/
Expand All @@ -6,25 +11,25 @@ export interface Contexts {
* List all contexts in the kubeconfig
* @returns a list of context names
*/
list(): Promise<string[]>; // TODO was getContextNames
list(): string[]; // TODO was getContextNames

/**
* Read the current context in the kubeconfig
* @returns the current context name
*/
readCurrent(): Promise<string>; // TODO was getCurrentContext
readCurrent(): string; // TODO was getCurrentContext

/**
* Read the current namespace in the kubeconfig
* @returns the current namespace name
*/
readCurrentNamespace(): Promise<string>; // TODO was getCurrentContextNamespace
readCurrentNamespace(): NamespaceName; // TODO was getCurrentContextNamespace

/**
* Set the current context in the kubeconfig
* @param context - the context name to set
*/
updateCurrent(context: string): Promise<void>; // TODO delete this once we are instantiating multiple K8 instances, was setCurrentContext
updateCurrent(context: string): void; // TODO delete this once we are instantiating multiple K8 instances, was setCurrentContext

/**
* Test the connection to a context
Expand Down
7 changes: 7 additions & 0 deletions src/core/kube/k8.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {type Containers} from './containers.js';
import {type Clusters} from './clusters.js';
import {type ConfigMaps} from './config_maps.js';
import {type ContainerRef} from './container_ref.js';
import {type Contexts} from './contexts.js';

export interface K8 {
/**
Expand All @@ -39,6 +40,12 @@ export interface K8 {
*/
configMaps(): ConfigMaps;

/**
* Fluent accessor for reading and manipulating contexts in the kubeconfig file.
* @returns an object instance providing context operations
*/
contexts(): Contexts;

/**
* Create a new namespace
* @param namespace - the namespace to create
Expand Down
37 changes: 16 additions & 21 deletions src/core/kube/k8_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import path from 'path';
import {Flags as flags} from '../../commands/flags.js';
import {MissingArgumentError, SoloError} from './../errors.js';
import type * as http from 'node:http';
import {getReasonPhrase, StatusCodes} from 'http-status-codes';
import {sleep} from './../helpers.js';
import * as constants from './../constants.js';
Expand All @@ -32,6 +31,9 @@
import {ContainerRef} from './container_ref.js';
import {K8ClientContainers} from './k8_client/k8_client_containers.js';
import {type Containers} from './containers.js';
import {type Contexts} from './contexts.js';
import type http from 'node:http';
import K8ClientContexts from './k8_client/k8_client_contexts.js';

/**
* A kubernetes API wrapper class providing custom functionalities required by solo
Expand All @@ -42,8 +44,6 @@
// TODO rename to K8Client and move to kube folder
@injectable()
export class K8Client implements K8 {
private cachedContexts: Context[];

static PodReadyCondition = new Map<string, string>().set(
constants.POD_CONDITION_READY,
constants.POD_CONDITION_STATUS_TRUE,
Expand All @@ -56,8 +56,8 @@

private k8Clusters: K8ClientClusters;
private k8ConfigMaps: K8ClientConfigMaps;

private k8Containers: K8ClientContainers;
private k8Contexts: K8ClientContexts;

constructor(
@inject(ConfigManager) private readonly configManager?: ConfigManager,
Expand Down Expand Up @@ -89,6 +89,7 @@
this.k8Clusters = new K8ClientClusters(this.kubeConfig);
this.k8ConfigMaps = new K8ClientConfigMaps(this.kubeClient);
this.k8Containers = new K8ClientContainers(this.kubeConfig);
this.k8Contexts = new K8ClientContexts(this.kubeConfig);

return this; // to enable chaining
}
Expand Down Expand Up @@ -122,6 +123,14 @@
return this.k8Containers;
}

/**
* Fluent accessor for reading and manipulating contexts in the kubeconfig file.
* @returns an object instance providing context operations
*/
public contexts(): Contexts {
return this.k8Contexts;
}

/**
* Apply filters to metadata
* @param items - list of items
Expand Down Expand Up @@ -288,21 +297,7 @@
}

public getContextNames(): string[] {
const contexts: string[] = [];

for (const context of this.getContexts()) {
contexts.push(context.name);
}

return contexts;
}

private getContexts(): Context[] {
if (!this.cachedContexts) {
this.cachedContexts = this.kubeConfig.getContexts();
}

return this.cachedContexts;
return this.contexts().list();
}

public async listDir(containerRef: ContainerRef, destPath: string) {
Expand Down Expand Up @@ -1096,11 +1091,11 @@
}

public getCurrentContext(): string {
return this.kubeConfig.getCurrentContext();
return this.contexts().readCurrent();

Check warning on line 1094 in src/core/kube/k8_client.ts

View check run for this annotation

Codecov / codecov/patch

src/core/kube/k8_client.ts#L1094

Added line #L1094 was not covered by tests
}

public getCurrentContextNamespace(): NamespaceName {
return NamespaceName.of(this.kubeConfig.getContextObject(this.getCurrentContext())?.namespace);
return this.contexts().readCurrentNamespace();

Check warning on line 1098 in src/core/kube/k8_client.ts

View check run for this annotation

Codecov / codecov/patch

src/core/kube/k8_client.ts#L1098

Added line #L1098 was not covered by tests
}

public getCurrentClusterName(): string {
Expand Down
4 changes: 2 additions & 2 deletions src/core/kube/k8_client/k8_client_clusters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default class K8ClientClusters implements Clusters {
}
}

list(): string[] {
public list(): string[] {
const clusters: string[] = [];
for (const cluster of this.kubeConfig.getClusters()) {
clusters.push(cluster.name);
Expand All @@ -21,7 +21,7 @@ export default class K8ClientClusters implements Clusters {
return clusters;
}

readCurrent(): string {
public readCurrent(): string {
const currentCluster = this.kubeConfig.getCurrentCluster();
return !currentCluster ? '' : currentCluster.name;
}
Expand Down
23 changes: 11 additions & 12 deletions src/core/kube/k8_client/k8_client_config_maps.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/**
* SPDX-License-Identifier: Apache-2.0
*/
import * as k8s from '@kubernetes/client-node';
import {type V1ConfigMap} from '@kubernetes/client-node';
import {type CoreV1Api, V1ConfigMap, V1ObjectMeta} from '@kubernetes/client-node';
import {type ConfigMaps} from '../config_maps.js';
import {type NamespaceName} from '../namespace_name.js';
import {
Expand All @@ -16,9 +15,9 @@ import {ResourceOperation} from '../resource_operation.js';
import {KubeApiResponse} from '../kube_api_response.js';

export default class K8ClientConfigMaps implements ConfigMaps {
constructor(private readonly kubeClient: k8s.CoreV1Api) {}
public constructor(private readonly kubeClient: CoreV1Api) {}

async create(
public async create(
namespace: NamespaceName,
name: string,
labels: Record<string, string>,
Expand All @@ -27,7 +26,7 @@ export default class K8ClientConfigMaps implements ConfigMaps {
return this.createOrReplaceWithForce(namespace, name, labels, data, false, true);
}

async createOrReplace(
public async createOrReplace(
namespace: NamespaceName,
name: string,
labels: Record<string, string>,
Expand All @@ -36,7 +35,7 @@ export default class K8ClientConfigMaps implements ConfigMaps {
return this.createOrReplaceWithForce(namespace, name, labels, data, false, false);
}

async delete(namespace: NamespaceName, name: string): Promise<boolean> {
public async delete(namespace: NamespaceName, name: string): Promise<boolean> {
try {
const resp = await this.kubeClient.deleteNamespacedConfigMap(name, namespace.name);
return KubeApiResponse.isFailingStatus(resp.response);
Expand All @@ -45,13 +44,13 @@ export default class K8ClientConfigMaps implements ConfigMaps {
}
}

async read(namespace: NamespaceName, name: string): Promise<V1ConfigMap> {
public async read(namespace: NamespaceName, name: string): Promise<V1ConfigMap> {
const {response, body} = await this.kubeClient.readNamespacedConfigMap(name, namespace.name).catch(e => e);
KubeApiResponse.check(response, ResourceOperation.READ, ResourceType.CONFIG_MAP, namespace, name);
return body as k8s.V1ConfigMap;
return body as V1ConfigMap;
}

async replace(
public async replace(
namespace: NamespaceName,
name: string,
labels: Record<string, string>,
Expand All @@ -60,7 +59,7 @@ export default class K8ClientConfigMaps implements ConfigMaps {
return this.createOrReplaceWithForce(namespace, name, labels, data, true, false);
}

async exists(namespace: NamespaceName, name: string): Promise<boolean> {
public async exists(namespace: NamespaceName, name: string): Promise<boolean> {
try {
const cm = await this.read(namespace, name);
return !!cm;
Expand All @@ -82,10 +81,10 @@ export default class K8ClientConfigMaps implements ConfigMaps {
forceCreate?: boolean,
): Promise<boolean> {
const replace = await this.shouldReplace(namespace, name, forceReplace, forceCreate);
const configMap = new k8s.V1ConfigMap();
const configMap = new V1ConfigMap();
configMap.data = data;

const metadata = new k8s.V1ObjectMeta();
const metadata = new V1ObjectMeta();
metadata.name = name;
metadata.namespace = namespace.name;
metadata.labels = labels;
Expand Down
14 changes: 7 additions & 7 deletions src/core/kube/k8_client/k8_client_container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class K8ClientContainer implements Container {
this.k8 = container.resolve('K8') as K8;
}

async copyFrom(srcPath: string, destDir: string): Promise<unknown> {
public async copyFrom(srcPath: string, destDir: string): Promise<unknown> {
const self = this;
const namespace = this.containerRef.podRef.namespaceName;
const guid = uuid4();
Expand Down Expand Up @@ -165,7 +165,7 @@ export class K8ClientContainer implements Container {
}
}

async copyTo(srcPath: string, destDir: string, filter: TarCreateFilter | undefined): Promise<boolean> {
public async copyTo(srcPath: string, destDir: string, filter: TarCreateFilter | undefined): Promise<boolean> {
const self = this;
const namespace = this.containerRef.podRef.namespaceName;
const guid = uuid4();
Expand Down Expand Up @@ -250,7 +250,7 @@ export class K8ClientContainer implements Container {
}
}

async execContainer(command: string | string[]): Promise<string> {
public async execContainer(command: string | string[]): Promise<string> {
const self = this;
const namespace = this.containerRef.podRef.namespaceName;
const guid = uuid4();
Expand Down Expand Up @@ -330,14 +330,14 @@ export class K8ClientContainer implements Container {
});
}

async hasDir(destPath: string): Promise<boolean> {
public async hasDir(destPath: string): Promise<boolean> {
return (
(await this.execContainer(['bash', '-c', '[[ -d "' + destPath + '" ]] && echo -n "true" || echo -n "false"'])) ===
'true'
);
}

async hasFile(destPath: string, filters: object): Promise<boolean> {
public async hasFile(destPath: string, filters: object): Promise<boolean> {
const parentDir = path.dirname(destPath);
const fileName = path.basename(destPath);
const filterMap = new Map(Object.entries(filters));
Expand Down Expand Up @@ -385,7 +385,7 @@ export class K8ClientContainer implements Container {
return false;
}

async listDir(destPath: string): Promise<any[] | TDirectoryData[]> {
public async listDir(destPath: string): Promise<any[] | TDirectoryData[]> {
// TODO future, return the following
// return this.pods.byName(podName).listDir(containerName, destPath);
// byName(podName) can use an underlying cache to avoid multiple calls to the API
Expand Down Expand Up @@ -447,7 +447,7 @@ export class K8ClientContainer implements Container {
}
}

async mkdir(destPath: string): Promise<string> {
public async mkdir(destPath: string): Promise<string> {
return this.execContainer(['bash', '-c', 'mkdir -p "' + destPath + '"']);
}

Expand Down
4 changes: 2 additions & 2 deletions src/core/kube/k8_client/k8_client_containers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import {type KubeConfig} from '@kubernetes/client-node';
* SPDX-License-Identifier: Apache-2.0
*/
export class K8ClientContainers implements Containers {
constructor(private readonly kubeConfig: KubeConfig) {}
public constructor(private readonly kubeConfig: KubeConfig) {}

readByRef(containerRef: ContainerRef): Container {
public readByRef(containerRef: ContainerRef): Container {
return new K8ClientContainer(this.kubeConfig, containerRef);
}
}
49 changes: 49 additions & 0 deletions src/core/kube/k8_client/k8_client_contexts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* SPDX-License-Identifier: Apache-2.0
*/
import {type Contexts} from '../contexts.js';
import {type KubeConfig, CoreV1Api} from '@kubernetes/client-node';
import {NamespaceName} from '../namespace_name.js';

export default class K8ClientContexts implements Contexts {
public constructor(private readonly kubeConfig: KubeConfig) {}

public list(): string[] {
const contexts: string[] = [];

for (const context of this.kubeConfig.getContexts()) {
contexts.push(context.name);
}

return contexts;
}

public readCurrent(): string {
return this.kubeConfig.getCurrentContext();
}

Check warning on line 23 in src/core/kube/k8_client/k8_client_contexts.ts

View check run for this annotation

Codecov / codecov/patch

src/core/kube/k8_client/k8_client_contexts.ts#L22-L23

Added lines #L22 - L23 were not covered by tests

public readCurrentNamespace(): NamespaceName {
return NamespaceName.of(this.kubeConfig.getContextObject(this.readCurrent())?.namespace);
}

Check warning on line 27 in src/core/kube/k8_client/k8_client_contexts.ts

View check run for this annotation

Codecov / codecov/patch

src/core/kube/k8_client/k8_client_contexts.ts#L26-L27

Added lines #L26 - L27 were not covered by tests

public updateCurrent(context: string): void {
this.kubeConfig.setCurrentContext(context);
}

Check warning on line 31 in src/core/kube/k8_client/k8_client_contexts.ts

View check run for this annotation

Codecov / codecov/patch

src/core/kube/k8_client/k8_client_contexts.ts#L30-L31

Added lines #L30 - L31 were not covered by tests

public async testContextConnection(context: string): Promise<boolean> {
const originalCtxName = this.readCurrent();
this.kubeConfig.setCurrentContext(context);

const tempKubeClient = this.kubeConfig.makeApiClient(CoreV1Api);
return await tempKubeClient
.listNamespace()
.then(() => {
this.kubeConfig.setCurrentContext(originalCtxName);
return true;
})
.catch(() => {
this.kubeConfig.setCurrentContext(originalCtxName);
return false;
});
}

Check warning on line 48 in src/core/kube/k8_client/k8_client_contexts.ts

View check run for this annotation

Codecov / codecov/patch

src/core/kube/k8_client/k8_client_contexts.ts#L34-L48

Added lines #L34 - L48 were not covered by tests
}
Loading