Skip to content

Commit

Permalink
Add a bunch of explicit types on 'public' interface
Browse files Browse the repository at this point in the history
  • Loading branch information
danopia committed Sep 17, 2024
1 parent 1449965 commit cce0489
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 26 deletions.
34 changes: 25 additions & 9 deletions lib/kubeconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class KubeConfig {
// Using this baseUrl
baseUrl = 'https://kubernetes.default.svc.cluster.local',
secretsPath = '/var/run/secrets/kubernetes.io/serviceaccount',
}={}) {
}={}): Promise<KubeConfig> {
// Avoid interactive prompting for in-cluster secrets.
// These are not commonly used from an interactive session.
const readPermission = await Deno.permissions.query({name: 'read', path: secretsPath});
Expand Down Expand Up @@ -81,7 +81,7 @@ export class KubeConfig {

static getSimpleUrlConfig({
baseUrl = 'http://localhost:8080',
}={}) {
}={}): KubeConfig {
return new KubeConfig({
'apiVersion': "v1",
'kind': "Config",
Expand All @@ -95,18 +95,29 @@ export class KubeConfig {
});
}

getContext(name?: string) {
getContext(name?: string): {
name: string;
context: ContextConfig;
} | null {
return name && this.data.contexts?.find(x => x.name === name) || null;
}
getCluster(name?: string) {

getCluster(name?: string): {
name: string;
cluster: ClusterConfig;
} | null {
return name && this.data.clusters?.find(x => x.name === name) || null;
}
getUser(name?: string) {

getUser(name?: string): {
name: string;
user: UserConfig;
} | null {
return name && this.data.users?.find(x => x.name === name) || null;
}

// client-go is really forgiving about incomplete configs, so let's act similar
fetchContext(contextName?: string) {
fetchContext(contextName?: string): KubeConfigContext {
const current = this.getContext(contextName ?? this.data["current-context"]);
const cluster = this.getCluster(current?.context?.cluster);
const user = this.getUser(current?.context?.user);
Expand All @@ -126,11 +137,13 @@ export class KubeConfigContext {
) {}
private execCred: ExecCredentialStatus | null = null;

get defaultNamespace() {
get defaultNamespace(): string | null {
return this.context.namespace ?? null;
}

async getServerTls() {
async getServerTls(): Promise<{
serverCert: string;
} | null> {
let serverCert = atob(this.cluster["certificate-authority-data"] ?? '') || null;
if (!serverCert && this.cluster["certificate-authority"]) {
serverCert = await Deno.readTextFile(this.cluster["certificate-authority"]);
Expand All @@ -142,7 +155,10 @@ export class KubeConfigContext {
return null;
}

async getClientTls() {
async getClientTls(): Promise<{
userKey: string;
userCert: string;
} | null> {
let userCert = atob(this.user["client-certificate-data"] ?? '') || null;
if (!userCert && this.user["client-certificate"]) {
userCert = await Deno.readTextFile(this.user["client-certificate"]);
Expand Down
17 changes: 15 additions & 2 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ export * from './lib/stream-transformers.ts';
export * from './transports/mod.ts';

/** Paginates through an API request, yielding each successive page as a whole */
export async function* readAllPages<T, U extends {continue?: string | null}>(pageFunc: (token?: string) => Promise<{metadata: U, items: T[]}>) {
export async function* readAllPages<T, U extends {continue?: string | null}>(
pageFunc: (token?: string) => Promise<{
metadata: U;
items: T[];
}>,
): AsyncGenerator<{
metadata: U;
items: T[];
}, void, unknown> {
let pageToken: string | undefined;
do {
const page = await pageFunc(pageToken ?? undefined);
Expand All @@ -16,7 +24,12 @@ export async function* readAllPages<T, U extends {continue?: string | null}>(pag
}

/** Paginates through an API request, yielding every individual item returned */
export async function* readAllItems<T>(pageFunc: (token?: string) => Promise<{metadata: {continue?: string | null}, items: T[]}>) {
export async function* readAllItems<T>(
pageFunc: (token?: string) => Promise<{
metadata: {continue?: string | null};
items: T[];
}>,
): AsyncGenerator<Awaited<T>, void, undefined> {
for await (const page of readAllPages(pageFunc)) {
yield* page.items;
}
Expand Down
4 changes: 2 additions & 2 deletions transports/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class ClientProviderChain {
/** Constructs the typical list of Kubernetes API clients,
* using an alternative client for connecting to KubeConfig contexts.
* The Kubectl client is unaffected by this. */
export function makeClientProviderChain(restClientFactory: KubeConfigClientFactory) {
export function makeClientProviderChain(restClientFactory: KubeConfigClientFactory): ClientProviderChain {
return new ClientProviderChain([
['InCluster', () => restClientFactory.forInCluster()],
['KubeConfig', () => restClientFactory.readKubeConfig()],
Expand All @@ -61,7 +61,7 @@ interface KubeConfigClientFactory {
): Promise<RestClient>;
}

export const DefaultClientProvider = makeClientProviderChain(KubeConfigRestClient);
export const DefaultClientProvider: ClientProviderChain = makeClientProviderChain(KubeConfigRestClient);

/**
* Automatic trial-and-error approach for deciding how to talk to Kubernetes.
Expand Down
9 changes: 7 additions & 2 deletions transports/via-kubeconfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TextLineStream } from '../deps.ts';
import { RestClient, RequestOptions, JSONValue } from '../lib/contract.ts';
import { RestClient, RequestOptions, JSONValue, KubernetesTunnel } from '../lib/contract.ts';
import { JsonParsingTransformer } from '../lib/stream-transformers.ts';
import { KubeConfig, KubeConfigContext } from '../lib/kubeconfig.ts';

Expand Down Expand Up @@ -94,7 +94,12 @@ export class KubeConfigRestClient implements RestClient {
return new this(ctx, httpClient);
}

async performRequest(opts: RequestOptions): Promise<any> {
performRequest(opts: RequestOptions & {expectTunnel: string[]}): Promise<KubernetesTunnel>;
performRequest(opts: RequestOptions & {expectStream: true; expectJson: true}): Promise<ReadableStream<JSONValue>>;
performRequest(opts: RequestOptions & {expectStream: true}): Promise<ReadableStream<Uint8Array>>;
performRequest(opts: RequestOptions & {expectJson: true}): Promise<JSONValue>;
performRequest(opts: RequestOptions): Promise<Uint8Array>;
async performRequest(opts: RequestOptions): Promise<unknown> {
let path = opts.path || '/';
if (opts.querystring) {
path += `?${opts.querystring}`;
Expand Down
14 changes: 11 additions & 3 deletions transports/via-kubectl-raw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ export class KubectlRawRestClient implements RestClient {
bodyJson?: JSONValue;
bodyStream?: ReadableStream<Uint8Array>;
bodyPassthru?: boolean;
}) {
}): Promise<[
Deno.ChildProcess,
Promise<Deno.CommandStatus>,
]> {

const hasReqBody = opts.bodyJson !== undefined || !!opts.bodyRaw || !!opts.bodyStream;
isVerbose && console.error('$ kubectl', args.join(' '), hasReqBody ? '< input' : '');
Expand Down Expand Up @@ -65,10 +68,15 @@ export class KubectlRawRestClient implements RestClient {
}
}

return [p, p.status] as const;
return [p, p.status];
}

async performRequest(opts: RequestOptions): Promise<any> {
performRequest(opts: RequestOptions & {expectTunnel: string[]}): Promise<KubernetesTunnel>;
performRequest(opts: RequestOptions & {expectStream: true; expectJson: true}): Promise<ReadableStream<JSONValue>>;
performRequest(opts: RequestOptions & {expectStream: true}): Promise<ReadableStream<Uint8Array>>;
performRequest(opts: RequestOptions & {expectJson: true}): Promise<JSONValue>;
performRequest(opts: RequestOptions): Promise<Uint8Array>;
async performRequest(opts: RequestOptions): Promise<unknown> {
const command = {
GET: 'get',
POST: 'create',
Expand Down
15 changes: 12 additions & 3 deletions tunnel-beta/via-spdy-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { KubeConfigRestClient } from "../transports/via-kubeconfig.ts";

export class SpdyEnabledRestClient extends KubeConfigRestClient {

async performRequest(opts: RequestOptions) {
performRequest(opts: RequestOptions & {expectTunnel: string[]}): Promise<KubernetesTunnel>;
performRequest(opts: RequestOptions & {expectStream: true; expectJson: true}): Promise<ReadableStream<JSONValue>>;
performRequest(opts: RequestOptions & {expectStream: true}): Promise<ReadableStream<Uint8Array>>;
performRequest(opts: RequestOptions & {expectJson: true}): Promise<JSONValue>;
performRequest(opts: RequestOptions): Promise<Uint8Array>;
async performRequest(opts: RequestOptions): Promise<unknown> {
if (!opts.expectTunnel) {
return super.performRequest(opts);
}
Expand Down Expand Up @@ -46,14 +51,18 @@ export class SpdyTunnel implements KubernetesTunnel {
public readonly subProtocol: string,
) {
}
readonly transportProtocol = "SPDY";
readonly transportProtocol: "SPDY" = "SPDY";

async getChannel<Treadable extends boolean, Twritable extends boolean>(opts: {
spdyHeaders?: Record<string, string | number> | undefined;
streamIndex?: number | undefined;
readable: Treadable;
writable: Twritable;
}) {
}): Promise<{
close: () => void;
writable: Twritable extends true ? WritableStream<Uint8Array> : null;
readable: Treadable extends true ? ReadableStream<Uint8Array> : null;
}> {
const { spdyHeaders } = opts;
if (!spdyHeaders) {
throw new Error("Cannot get a SPDY channel without spdyHeaders.");
Expand Down
18 changes: 13 additions & 5 deletions tunnel-beta/via-websocket.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { map } from "https://deno.land/x/[email protected]/transforms/map.ts";
import { EOF, external } from "https://deno.land/x/[email protected]/sources/external.ts";

import { KubernetesTunnel, RequestOptions } from "../lib/contract.ts";
import { JSONValue, KubernetesTunnel, RequestOptions } from "../lib/contract.ts";
import { KubeConfigRestClient } from "../transports/via-kubeconfig.ts";

/**
Expand All @@ -28,7 +28,12 @@ import { KubeConfigRestClient } from "../transports/via-kubeconfig.ts";
* Upstream work: https://github.com/kubernetes/kubernetes/pull/119157
*/
export class WebsocketRestClient extends KubeConfigRestClient {
async performRequest<Tproto extends string>(opts: RequestOptions & {expectTunnel?: Tproto[]}) {
performRequest(opts: RequestOptions & {expectTunnel: string[]}): Promise<KubernetesTunnel>;
performRequest(opts: RequestOptions & {expectStream: true; expectJson: true}): Promise<ReadableStream<JSONValue>>;
performRequest(opts: RequestOptions & {expectStream: true}): Promise<ReadableStream<Uint8Array>>;
performRequest(opts: RequestOptions & {expectJson: true}): Promise<JSONValue>;
performRequest(opts: RequestOptions): Promise<Uint8Array>;
async performRequest<Tproto extends string>(opts: RequestOptions & {expectTunnel?: Tproto[]}): Promise<unknown> {
const requestedProtocols = opts.expectTunnel;
if (!requestedProtocols) {
return super.performRequest(opts);
Expand Down Expand Up @@ -95,7 +100,7 @@ export class WebsocketTunnel implements KubernetesTunnel {
}
});
}
readonly transportProtocol = "WebSocket";
readonly transportProtocol: "WebSocket" = "WebSocket";
readonly subProtocol: string;
readonly done: Promise<void>;

Expand All @@ -107,7 +112,10 @@ export class WebsocketTunnel implements KubernetesTunnel {
streamIndex?: number | undefined;
readable: Treadable;
writable: Twritable;
}) {
}): Promise<{
writable: Twritable extends true ? WritableStream<Uint8Array> : null;
readable: Treadable extends true ? ReadableStream<Uint8Array> : null;
}> {
const { streamIndex } = opts;
if (typeof streamIndex !== 'number') {
throw new Error("Cannot get a WebSocket channel without a streamIndex.");
Expand All @@ -134,7 +142,7 @@ export class WebsocketTunnel implements KubernetesTunnel {
return Promise.resolve();
}

stop() {
stop(): Promise<void> {
this.wss.close();
return Promise.resolve();
}
Expand Down

0 comments on commit cce0489

Please sign in to comment.