Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions lib/interface/cli/commands/root/create.cmd.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const Command = require('../../Command');
const { crudFilenameOption } = require('../../helpers/general');
const { context, pipeline } = require('../../../../logic').api;
const yargs = require('yargs');
const { validatePipelineFile } = require('../../helpers/validation');
const { validatePipelineSpec } = require('../../helpers/validation');

const get = new Command({
root: true,
Expand Down Expand Up @@ -40,10 +40,9 @@ const get = new Command({
console.log(`Context: ${name} created`);
break;
case 'pipeline':
try {
await validatePipelineFile(data);
} catch (e) {
console.warn(e.message);
const result = await validatePipelineSpec(data);
if (!result.valid) {
console.warn(result.message);
return;
}
await pipeline.createPipeline(data);
Expand Down
9 changes: 4 additions & 5 deletions lib/interface/cli/commands/root/replace.cmd.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const Command = require('../../Command');
const { crudFilenameOption } = require('../../helpers/general');
const { context, pipeline } = require('../../../../logic').api;
const yargs = require('yargs');
const { validatePipelineFile } = require('../../helpers/validation');
const { validatePipelineSpec } = require('../../helpers/validation');

const annotate = new Command({
root: true,
Expand Down Expand Up @@ -39,10 +39,9 @@ const annotate = new Command({
console.log(`Context: ${name} created`);
break;
case 'pipeline':
try {
await validatePipelineFile(data);
} catch (e) {
console.warn(e.message);
const result = await validatePipelineSpec(data);
if (!result.valid) {
console.warn(result.message);
return;
}
await pipeline.replaceByName(name, data);
Expand Down
95 changes: 95 additions & 0 deletions lib/interface/cli/commands/root/validate.cmd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
const Command = require('../../Command');
const fs = require('fs');
const _ = require('lodash');
const Style = require('../../../../output/style');
const path = require('path');
const { validatePipelineYaml } = require('../../helpers/validation');
const { pathExists } = require('../../helpers/general');


const VALID_MESSAGE = Style.green('Yaml is valid!');
function _printResult(result) {
console.log(result.valid ? VALID_MESSAGE : Style.red(result.message));
}


const validateCmd = new Command({
root: true,
command: 'validate [filenames..]',
description: 'Validate codefresh pipeline yaml config files.',
usage: 'Validate one or many pipeline yaml files or attach validator to one and validate on changes',
webDocs: {
description: 'Validate codefresh pipeline yaml config files',
category: 'Validation',
title: 'Validate pipeline yaml',
weight: 100,
},
builder: (yargs) => {
yargs
.positional('filenames', {
describe: 'Paths to yaml files',
required: true,
})
.option('attach', {
alias: 'a',
describe: 'Attach validator to the file and validate on change',
});


return yargs;
},
handler: async (argv) => {
let { filenames, attach } = argv;
filenames = filenames.map(f => path.resolve(process.cwd(), f));

if (_.isEmpty(filenames)) {
console.log('No filename not provided!');
return;
}

const allExist = (
await Promise.all(filenames.map(f => pathExists(f)))
).reduce((a, b) => a && b);

if (!allExist) {
return;
}

if (filenames.length > 1) {
if (attach) {
console.log('Cannot watch many files!');
return;
}

filenames.forEach(f => validatePipelineYaml(f).then((result) => {
console.log(`Validation result for ${f}:`);
_printResult(result);
console.log();
}));
return;
}

const filename = filenames[0];
if (attach) {
console.log(`Validator attached to file: ${filename}`);
fs.watchFile(filename, { interval: 500 }, async () => {
console.log('File changed');
const result = await validatePipelineYaml(filename);
_printResult(result);
console.log();
});

const unwatcher = f => () => fs.unwatchFile(f);
['exit', 'SIGINT', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'SIGTERM'].forEach((eventType) => {
process.on(eventType, unwatcher(filename));
});
}

// even with --attach option validates file for first time
const result = await validatePipelineYaml(filename);
_printResult(result);
console.log();
},
});

module.exports = validateCmd;
20 changes: 20 additions & 0 deletions lib/interface/cli/helpers/general.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,30 @@ const crudFilenameOption = (yargs, options = {}) => {
});
};

function pathExists(p) {
return new Promise(resolve => fs.access(p, resolve))
.then((err) => {
if (err) {
console.log(`File does not exist: ${p}`);
return false;
}
return true;
});
}

function readFile(filename) {
return new Promise((resolve, reject) => fs.readFile(filename, 'UTF-8', (err, data) => {
if (err) reject(err);
else resolve(data);
}));
}

module.exports = {
printError,
wrapHandler,
prepareKeyValueFromCLIEnvOption,
crudFilenameOption,
prepareKeyValueCompostionFromCLIEnvOption,
pathExists,
readFile,
};
41 changes: 29 additions & 12 deletions lib/interface/cli/helpers/validation.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
const _ = require('lodash');
const yaml = require('js-yaml');
const { pipeline } = require('../../../logic').api;
const { readFile } = require('./general');


async function validatePipelineFile(data) {
function _buildFinalMessage(baseMessage, validationResult) {
if (_.isArray(validationResult.details)) {
const errors = validationResult.details
.map(({ message }) => ` - ${message}`)
.join('\n');
return `${baseMessage}:\n${errors}`;
}
return `${baseMessage}!`;
}

async function validatePipelineSpec(data) {
const validatedYaml = yaml.safeDump(Object.assign({ version: data.version }, data.spec));
const validationResult = await pipeline.validateYaml(validatedYaml);
if (!validationResult.valid) {
let finalMessage;
if (_.isArray(validationResult.details)) {
const errors = validationResult.details.map(({ message }) => ` - ${message}`).join('\n');
finalMessage = `Provided spec is not valid:\n${errors}`;
} else {
finalMessage = 'Provided spec is not valid!';
}
throw new Error(finalMessage);
const result = await pipeline.validateYaml(validatedYaml);
let message;
if (!result.valid) {
message = _buildFinalMessage('Provided spec is not valid', result);
}
return { valid: !!result.valid, message };
}

async function validatePipelineYaml(filename) {
const file = await readFile(filename);
const result = await pipeline.validateYaml(file);
let message;
if (!result.valid) {
message = _buildFinalMessage('Yaml not valid', result);
}
return { valid: !!result.valid, message };
}

module.exports = {
validatePipelineFile,
validatePipelineSpec,
validatePipelineYaml,
};