Created
March 30, 2020 16:13
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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