Skip to content

Instantly share code, notes, and snippets.

@dflemstr
Created March 30, 2020 16:13
Show Gist options
  • Save dflemstr/54d28137a8a8b415112fa4a93c489cab to your computer and use it in GitHub Desktop.
Save dflemstr/54d28137a8a8b415112fa4a93c489cab to your computer and use it in GitHub Desktop.
A kpt function that wraps kubeval and emits validation errors as kpt errors
import {Configs, KubernetesObject, KubernetesObjectError, MultiConfigError} from 'kpt-functions';
import {ChildProcess, spawn} from 'child_process';
import {Writable} from "stream";
const SCHEMA_LOCATION = 'schema_location';
const ADDITIONAL_SCHEMA_LOCATIONS = 'additional_schema_locations';
const IGNORE_MISSING_SCHEMAS = 'ignore_missing_schemas';
const SKIP_KINDS = 'skip_kinds';
const STRICT = 'strict';
type Feedback = FeedbackItem[];
type FeedbackItem = {
filename: string,
kind: string,
status: "valid" | "invalid",
errors: string[],
};
export async function kubeval(configs: Configs) {
const schemaLocation = configs.getFunctionConfigValue(SCHEMA_LOCATION);
const additionalSchemaLocations = configs.getFunctionConfigValue(ADDITIONAL_SCHEMA_LOCATIONS)?.split(',');
const ignoreMissingSchemas = JSON.parse(configs.getFunctionConfigValue(IGNORE_MISSING_SCHEMAS) ?? 'false');
const skipKinds = configs.getFunctionConfigValue(SKIP_KINDS)?.split(',');
const strict = JSON.parse(configs.getFunctionConfigValue(STRICT) ?? 'false');
const validationErrors: KubernetesObjectError[] = [];
for (const object of configs.getAll()) {
await runKubeval(object, validationErrors, schemaLocation, additionalSchemaLocations, ignoreMissingSchemas, skipKinds, strict);
}
if (validationErrors.length > 0) {
throw new MultiConfigError('kubeval validation failed', validationErrors);
}
}
const runKubeval = async (
object: KubernetesObject,
validationErrors: KubernetesObjectError[],
schemaLocation?: string,
additionalSchemaLocations?: string[],
ignoreMissingSchemas?: boolean,
skipKinds?: string[],
strict?: boolean,
) => {
const args = ['--output', 'json'];
if (schemaLocation) {
args.push('--schema-location');
args.push(schemaLocation);
}
if (additionalSchemaLocations) {
args.push('--additional-schema-locations');
args.push(additionalSchemaLocations.join(','));
}
if (ignoreMissingSchemas) {
args.push('--ignore-missing-schemas');
}
if (skipKinds) {
args.push('--skip-kinds');
args.push(skipKinds.join(','));
}
if (strict) {
args.push('--strict');
}
const kubevalProcess = spawn(
'kubeval', args, {stdio: ['pipe', 'pipe', process.stderr]});
const serializedObject = JSON.stringify(object);
await writeToStream(kubevalProcess.stdin, serializedObject);
kubevalProcess.stdin.end();
const rawOutput = await readStdoutToString(kubevalProcess);
try {
const feedback = JSON.parse(rawOutput) as Feedback;
for (const {status, errors} of feedback) {
if (status !== 'valid') {
for (const error of errors) {
validationErrors.push(new KubernetesObjectError(error, object));
}
}
}
} catch (error) {
validationErrors.push(new KubernetesObjectError(
'Failed to parse raw kubeval output:\n' + error.message + '\n\n' + rawOutput, object));
}
};
const writeToStream = (stream: Writable, string: string): Promise<void> =>
new Promise((resolve, reject) =>
stream.write(string, 'utf-8', err => err ? reject(err) : resolve()));
const readStdoutToString = (childProcess: ChildProcess): Promise<string> =>
new Promise<string>((resolve, reject) => {
let result = '';
childProcess.stdout?.on('data', data => {
result += data.toString();
});
childProcess.on('close', () => {
resolve(result);
});
});
kubeval.usage = `
Validates configuration using kubeval.
Configured using a ConfigMap with the following keys:
${SCHEMA_LOCATION}: Comma-seperated list of secondary base URLs used to download schemas.
${ADDITIONAL_SCHEMA_LOCATIONS}: List of secondary base URLs used to download schemas.
${IGNORE_MISSING_SCHEMAS}: Skip validation for resource definitions without a schema.
${SKIP_KINDS}: Comma-separated list of case-sensitive kinds to skip when validating against schemas.
${STRICT}: Disallow additional properties not in schema.
`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment