projects/alloydb-autoscaler/src/autoscaler-config-validator/config-validator-cli.ts (106 lines of code) (raw):
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// eslint-disable-next-line n/no-unpublished-import
import * as yaml from 'js-yaml';
import * as fs from 'node:fs';
import {
ConfigValidator,
ValidationError,
} from '../autoscaler-core/poller/config-validator';
/**
* Validates the specified Autoscaler JSON configuration file.
* Throws an Error and reports to console if the config is not valid.
* @param configValidator Config Validator to use for validation.
* @param filename Filepath of the JSON file to validate.
*/
function assertValidJsonFile(
configValidator: ConfigValidator,
filename: string
) {
try {
const configText = fs.readFileSync(filename, 'utf-8');
configValidator.parseAndAssertValidConfig(configText);
} catch (e) {
if (e instanceof ValidationError) {
console.error(
`Validation of config in file ${filename} failed:\n${e.message}`
);
} else {
console.error(`Processing of config in file ${filename} failed: ${e}`);
}
throw new Error(`${filename} Failed validation`);
}
}
/**
* Validates all the Autoscaler YAML config files specified in the
* GKE configMap. Throws an Error and reports to console if any of the
* configmaps do not pass validation.
* @param configValidator Config Validator to use for validation.
* @param filename Filepath of the GKE config map file to validate.
*/
function assertValidGkeConfigMapFile(
configValidator: ConfigValidator,
filename: string
) {
// Allow any type since the configMap is not in our control.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let configMap: any;
try {
const configText = fs.readFileSync(filename, 'utf-8');
configMap = yaml.load(configText) as object;
} catch (e) {
console.error(`Could not parse YAML from ${filename}: ${e}`);
throw e;
}
if (configMap?.kind !== 'ConfigMap') {
console.error(`${filename} is not a GKE ConfigMap`);
throw new Error(`${filename} is not a GKE ConfigMap`);
}
let success = true;
for (const configMapFile of Object.keys(configMap?.data)) {
const configMapData = configMap?.data[configMapFile];
try {
const config = yaml.load(configMapData);
configValidator.parseAndAssertValidConfig(JSON.stringify(config));
} catch (e) {
if (e instanceof ValidationError) {
console.error(
`Validation of configMap entry data.${configMapFile} in file ` +
`${filename} failed:\n${e.message}`
);
} else if (e instanceof yaml.YAMLException) {
console.error(
'Could not parse YAML from value data. ' +
`${configMapFile} in ${filename}: ${e}`
);
} else {
console.error(
`Processing of configMap entry data.${configMapFile} in file ` +
`${filename} failed: ${e}`
);
}
success = false;
}
}
if (!success) {
throw new Error(`${filename} Failed validation`);
}
}
/**
* Validates a configuration file is valid.
* @param configValidator Config Validator to use for validation.
* @param filename Filepath of the GKE config map file to validate.
*/
function validateFile(configValidator: ConfigValidator, filename: string) {
if (filename.toLowerCase().endsWith('.yaml')) {
assertValidGkeConfigMapFile(configValidator, filename);
} else if (filename.toLowerCase().endsWith('.json')) {
assertValidJsonFile(configValidator, filename);
} else {
throw new Error(
`filename ${filename} must either be JSON (.json) or a YAML ` +
'configmap (.yaml) file'
);
}
}
/**
* Validates a configuration file passed in on the command line.
*
* Usage:
* validate-config-file <CONFIG_FILE_NAME>
*
* TODO: decide how to create CLI validators for derived configs (e.g. AlloyDB).
* @param configValidator Config validator to use for validation.
*/
export function main(configValidator: ConfigValidator = new ConfigValidator()) {
if (
process.argv.length <= 1 ||
process.argv[1] === '-h' ||
process.argv[1] === '--help'
) {
console.log('Usage: validate-config-file CONFIG_FILE_NAME');
console.log(
'Validates that the specified Autoscaler JSON config is defined ' +
'correctly'
);
process.exitCode = 1;
return;
}
const filename = process.argv[1];
try {
validateFile(configValidator, filename);
process.exitCode = 0;
} catch (e) {
process.exitCode = 1;
throw e;
}
}
export const TEST_ONLY = {validateFile};