Skip to content

Commit

Permalink
Added documentation (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
itowlson authored May 7, 2019
1 parent 4f63fd5 commit bfeb23b
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 2 deletions.
122 changes: 122 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,128 @@
A NPM package containing utility functions for parsing output from the Kubernetes `kubectl` command
line tool.

# Reference

## Table format functions

These functions are for use with `kubectl` commands that return data in tabular format -
that is, of the form:

```
TITLE1 TITLE2 TITLE3
value11 value12 value13
value21 value22 value23
```

where no column header or value contains a space, and columns are separated by one or more spaces.
Examples include the default behaviour of `kubectl get`, for example `kubectl get pods` or
`kubectl get pods -o wide` to get a list of pods.

In all cases:

* The function takes a `KubectlOutput`: that is, either a `ShellResult` (an object with numeric `code`,
`stdout` string and `stderr` string properties), or `undefined`. `code` and `stderr` are used
for error handling; on the happy path, the function will operate on the `stdout` string.
* The function returns an `Errorable`: that is, an object with a boolean `succeeded` property. If
`succeeded` is true, the object also has a `result` property containing the result of the function;
if `succeeded` is false, the object has `reason` and `error` properties describing the failure.
* If the input is `undefined`, or has a non-zero `code`, the function returns a _failed_ Errorable.
* The function does not check the format of the provided `stdout`. You should use it **only** on the output
of `kubectl` commands that return tabular data without spaces or missing values.

### parseTabular

**JavaScript:** `parseTabular(output)`

**TypeScript:** `parseTabular(output: KubectlOutput): Errorable<Dictionary<string>[]>`

Parses tabular `kubectl` output into an array of key-value objects, one per line.

The result is an array of the form:

```javascript
[
{
title1: "value11",
title2: "value12",
title3: "value13"
},
{
title1: "value21",
title2: "value22",
title3: "value23"
}
]
```

Each non-header row is parsed as an object within the array. Each object's keys are the lowercased
column headers, and the value of each key is the string under that header in the object's row.

If `output.stdout` is empty then the function returns success with an empty array.

**TypeScript:** `Dictionary<T>` is an alias for `{ [key: string]: T }`.

### asTableLines

**JavaScript:** `asTableLines(output)`

**TypeScript:** `asTableLines(output: KubectlOutput): Errorable<TableLines>`

Splits tabular `kubectl` output into a header line and an array of body lines. The result is
an object of the form:

```javascript
{
header: "TITLE1 TITLE2 TITLE3",
body: [
"value11 value12 value13".
"value21 value22 value23"
]
}
```

If `output.stdout` is empty then the function returns success with an empty string `header` and
an empty `body` array.

## JSON format functions

These functions are for use with `kubectl` commands that return data in JSON format.
This is typically triggered by the `-o json` option and may be used for lists or for single
resources, e.g. `kubectl get pods -o json` or `kubectl get deploy/nginx -o json`.

In all cases:

* The function takes a `KubectlOutput`: that is, either a `ShellResult` (an object with numeric `code`,
`stdout` string and `stderr` string properties), or `undefined`. `code` and `stderr` are used
for error handling; on the happy path, the function will operate on the `stdout` string.
* The function returns an `Errorable`: that is, an object with a boolean `succeeded` property. If
`succeeded` is true, the object also has a `result` property containing the result of the function;
if `succeeded` is false, the object has `reason` and `error` properties describing the failure.
* If the input is `undefined`, or has a non-zero `code`, or if `stdout` is empty, the function
returns a _failed_ Errorable.
* The function does not check the format of the provided `stdout`. You should use it **only** on the output
of `kubectl` commands that return JSON data.

### parseJSON

**JavaScript:** `parseJSON(output)`

**TypeScript:** `parseJSON<T>(output: KubectlOutput): Errorable<T>`

Checks for `kubectl` failure and then returns the deserialised object corresponding to the
`stdout` JSON.

### parseJSONCollection

**TypeScript:** `parseJSONCollection<T>(output: KubectlOutput): Errorable<KubernetesList<T>>`

Checks for `kubectl` failure and then returns the deserialised object corresponding to the
`stdout` JSON, where this is a Kubernetes item list object.

This is equivalent to writing `parseJSON<KubernetesList<T>>`; it is provided for TypeScript
users to reduce generics clutter in their code. (In untyped JavaScript, there's no difference
between this and the `parseJSON` function.)

# Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a
Expand Down
8 changes: 8 additions & 0 deletions ts/src/dictionary.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
/**
* Represents objects which can have arbitrary property names but whose
* properties must all be of the specified type. This is equivalent
* to the TypeScript `{ [key: string]: T }` type.
*/
export type Dictionary<T> = {
[key: string]: T
};

export module Dictionary {
/**
* Returns a new, empty Dictionary<T>.
*/
export function of<T>(): Dictionary<T> {
return {};
}
Expand Down
44 changes: 44 additions & 0 deletions ts/src/errorable.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,64 @@
/**
* Represents a successful result of a function on kubectl output - that is, the function
* successfully computed a value.
*/
export interface Succeeded<T> {
/**
* Identifies this as a successful result.
*/
readonly succeeded: true;
/**
* The result value of the function.
*/
readonly result: T;
}

/**
* Represents a failed result of a function on kubectl output - that is, the function was
* unable to compute a value, and this object describes why.
*/
export interface Failed {
/**
* Identifies this as a failed result.
*/
readonly succeeded: false;
/**
* The reason for the failure. This is a programmatic identifier which you
* can use to create a meaningful error message. Values are:
*
* 'failed-to-run': failed because the kubectl process was not created
* 'kubectl-error': failed because kubectl encountered an error (returned a non-zero exit code)
* 'failed-to-parse': failed because it could not parse kubectl's standard output
*/
readonly reason: 'failed-to-run' | 'kubectl-error' | 'failed-to-parse';
/**
* If reason is 'kubectl-error', contains the stderr from kubectl. Otherwise,
* contains a default message for the reason.
*/
readonly error: string;
}

/**
* Represents the result of trying to parse kubectl output - success or failure.
*/
export type Errorable<T> = Succeeded<T> | Failed;

/**
* Checks if an Errorable represents a successful parse. In TypeScript, this is
* a type guard, and you may access the .result property after calling this.
* @param e The Errorable to be checked.
* @returns Whether the Errorable represents a successful parse.
*/
export function succeeded<T>(e: Errorable<T>): e is Succeeded<T> {
return e.succeeded;
}

/**
* Checks if an Errorable represents a fauked parse. In TypeScript, this is
* a type guard, and you may access the .reason and .error properties after calling this.
* @param e The Errorable to be checked.
* @returns Whether the Errorable represents a failed parse.
*/
export function failed<T>(e: Errorable<T>): e is Failed {
return !e.succeeded;
}
9 changes: 8 additions & 1 deletion ts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
/**
* The result of invoking an external program via the shell.
*/
export interface ShellResult {
readonly code: number;
readonly stdout: string;
readonly stderr: string;
}

/**
* The result of invoking the kubectl program via the shell.
* This is undefined if creating the kubectl process failed.
*/
export type KubectlOutput = ShellResult | undefined;

export * from './dictionary';
export * from './errorable';
export { asTableLines, parseTabular, parseTableLines, TableLines } from './table';
export { asTableLines, parseTabular, TableLines } from './table';
export { parseJSON, parseJSONCollection, KubernetesList } from './json';
30 changes: 30 additions & 0 deletions ts/src/json.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
import { KubectlOutput, Errorable } from ".";
import { Dictionary } from "./dictionary";

/**
* Represents how kubectl -o json formats lists of resources.
*/
export interface KubernetesList<T> {
/**
* The Kubernetes API version.
*/
readonly apiVersion: string;
/**
* The contents of the list.
*/
readonly items: T[];
/**
* Identifies this object to the Kubernetes API as a list.
*/
readonly kind: "List";
/**
* Contains additional data about the list.
*/
readonly metadata: Dictionary<string>;
}

/**
* Parses JSON kubectl output into an object.
* @param output The result of invoking kubectl via the shell.
* @returns If kubectl ran successfully and produced JSON output, a success value
* containing the deserialised object. If kubectl did not run
* successfully, a failure value.
*/
export function parseJSON<T>(output: KubectlOutput): Errorable<T> {
if (!output) {
return { succeeded: false, reason: 'failed-to-run', error: 'Unable to run kubectl' };
Expand All @@ -24,6 +46,14 @@ export function parseJSON<T>(output: KubectlOutput): Errorable<T> {
return { succeeded: false, reason: 'kubectl-error', error: output.stderr };
}

/**
* Parses JSON kubectl output into a Kubernetes list object. You may use this if your
* kubectl command requested a list of resources rather than a single resource.
* @param output The result of invoking kubectl via the shell.
* @returns If kubectl ran successfully and produced JSON output, a success value
* containing the deserialised object. If kubectl did not run
* successfully, a failure value.
*/
export function parseJSONCollection<T>(output: KubectlOutput): Errorable<KubernetesList<T>> {
return parseJSON<KubernetesList<T>>(output);
}
23 changes: 22 additions & 1 deletion ts/src/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,23 @@ import { Errorable, failed } from "./errorable";

const KUBECTL_OUTPUT_COLUMN_SEPARATOR = /\s+/g;

/**
* Provides a line-oriented view of tabular kubectl output.
*/
export interface TableLines {
readonly header: string;
readonly body: string[];
}

/**
* Parses tabular kubectl output into an array of objects. Each non-header row
* is mapped to an object in the output, and each object has a property per column,
* named as the lower-cased column header.
* @param output The result of invoking kubectl via the shell.
* @returns If kubectl ran successfully and produced tabular output, a success value
* containing an array of objects for the non-header rows. If kubectl did not run
* successfully, a failure value.
*/
export function parseTabular(output: KubectlOutput): Errorable<Dictionary<string>[]> {
const table = asTableLines(output);
if (failed(table)) {
Expand All @@ -19,6 +31,15 @@ export function parseTabular(output: KubectlOutput): Errorable<Dictionary<string
return { succeeded: true, result: parsed };
}

/**
* Parses tabular kubectl output into an array of lines. The first row is mapped
* as a header, and the remaining rows as a body array.
* @param output The result of invoking kubectl via the shell.
* @returns If kubectl ran successfully and produced tabular output, a success value
* containing an object with header (string) and body (array of string) properties.
* If kubectl ran successfully but produced no output, header is the empty string and
* body the empty array. If kubectl did not run successfully, a failure value.
*/
export function asTableLines(output: KubectlOutput): Errorable<TableLines> {
if (!output) {
return { succeeded: false, reason: 'failed-to-run', error: 'Unable to run kubectl' };
Expand All @@ -37,7 +58,7 @@ export function asTableLines(output: KubectlOutput): Errorable<TableLines> {
return { succeeded: false, reason: 'kubectl-error', error: output.stderr };
}

export function parseTableLines(table: TableLines, columnSeparator: RegExp): Dictionary<string>[] {
function parseTableLines(table: TableLines, columnSeparator: RegExp): Dictionary<string>[] {
if (table.header.length === 0 || table.body.length === 0) {
return [];
}
Expand Down

0 comments on commit bfeb23b

Please sign in to comment.