Skip to content

Commit

Permalink
fix: parse the options for helm template command
Browse files Browse the repository at this point in the history
 - in order to allow to pass options to `helm template ...` command,
   we're parsing the options from a way that yargs accept to how
   `helm template` expect.
 - we're not yet pass the options list to `helm tempate`
  • Loading branch information
Arthur Granado committed Nov 13, 2019
1 parent ee7050b commit 2eaee7e
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 47 deletions.
111 changes: 97 additions & 14 deletions src/__tests__/test-cli-args.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,115 @@
import { IArgs, parseInputParameters } from "../cli-args";
import fs = require("fs");

let mockProcessExit;
let consoleMock;

beforeEach(() => {
//@ts-ignore
mockProcessExit = jest.spyOn(process, "exit").mockImplementation(code => {});
consoleMock = jest.spyOn(console, 'error').mockImplementation(jest.fn());
});

afterEach(() => {
consoleMock.mockRestore();
mockProcessExit.mockRestore();
});

describe("handle helm template options", () => {
let path;
let chartPath;

beforeEach(() => {
path = ".";
chartPath = `${path}/Chart.yaml`;
fs.writeFileSync(chartPath, "");
});

afterEach(() => {
fs.unlinkSync(chartPath);
});

describe("when without options", () => {
test("execute `helm snyk test .`", () => {
const inputArgs = ["test", path];
const expectedOptions = "";

const parsedArgs = parseInputParameters(inputArgs);

expect(parsedArgs.helmTemplateOptions).toBe(expectedOptions);
});
});

describe("when with options", () => {
test("execute `helm snyk test . --set='key1=val1,key2=val2'`", () => {
const helmOption = "--set='key1=val1,key2=val2'";
const inputArgs = ["test", path, helmOption];
const expectedOptions = "--set key1=val1,key2=val2";

const parsedArgs = parseInputParameters(inputArgs);

expect(parsedArgs.helmTemplateOptions).toBe(expectedOptions);
});

test("execute `helm snyk test . --set-file='key1=path1,key2=path2'`", () => {
const helmOption = "--set-file='key1=path1,key2=path2'";
const inputArgs = ["test", path, helmOption];
const expectedOptions = "--set-file key1=path1,key2=path2";

const parsedArgs = parseInputParameters(inputArgs);

expect(parsedArgs.helmTemplateOptions).toBe(expectedOptions);
});

test("execute `helm snyk test . --set-string='key1=val1,key2=val2'`", () => {
const helmOption = "--set-string='key1=val1,key2=val2'";
const inputArgs = ["test", path, helmOption];
const expectedOptions = "--set-string key1=val1,key2=val2";

const parsedArgs = parseInputParameters(inputArgs);

expect(parsedArgs.helmTemplateOptions).toBe(expectedOptions);
});

test("execute `helm snyk test . --values='file1,file2'`", () => {
const helmOption = "--values='file1,file2'";
const inputArgs = ["test", path, helmOption];
const expectedOptions = "--values file1 --values file2";

const parsedArgs = parseInputParameters(inputArgs);

expect(parsedArgs.helmTemplateOptions).toBe(expectedOptions);
});

test("execute `helm snyk test . --set='key1=val1,key2=val2' --values='file1,file2'`", () => {
const helmOption1 = "--set='key1=val1,key2=val2'";
const helmOption2 = "--values='file1,file2'";
const inputArgs = ["test", path, helmOption1, helmOption2];
const expectedOptions = "--set key1=val1,key2=val2 --values file1 --values file2";

const parsedArgs = parseInputParameters(inputArgs);

expect(parsedArgs.helmTemplateOptions).toBe(expectedOptions);
});
});
});

describe("test command", () => {
describe("check required input directory", () => {
test("process exit if there is no <chart-directory> required arg", () => {
const inputArgs = ["test"];
//@ts-ignore
const mockProcessExit = jest.spyOn(process, "exit").mockImplementation(code => {});


parseInputParameters(inputArgs);

expect(mockProcessExit).toHaveBeenCalledWith(1);
mockProcessExit.mockRestore();
});

test("handles error when directory is invalid", () => {
const inputArgs = ["test", "/not-valid-folder"];
//@ts-ignore
const mockProcessExit = jest.spyOn(process, "exit").mockImplementation(code => {});

parseInputParameters(inputArgs);

expect(mockProcessExit).toHaveBeenCalledWith(1);
mockProcessExit.mockRestore();
});

test("handles dot as input", () => {
Expand All @@ -32,7 +121,6 @@ describe("test command", () => {
const parsedArgs = parseInputParameters(inputArgs);

expect(parsedArgs.inputDirectory).toBe(".");
expect(parsedArgs.debug).toBe(false);

fs.unlinkSync(chartPath);
});
Expand All @@ -46,8 +134,6 @@ describe("test command", () => {

expect(parsedArgs.inputDirectory).toBe(path);

expect(parsedArgs.debug).toBe(false);

fs.unlinkSync(chartPath);
});

Expand All @@ -60,21 +146,18 @@ describe("test command", () => {
const parsedArgs = parseInputParameters(inputArgs);

expect(parsedArgs.inputDirectory).toBe(path);
expect(parsedArgs.debug).toBe(false);

fs.unlinkSync(chartPath);
});
});
});

test("yargs causes process exit if no args", () => {
//@ts-ignore
const mockProcessExit = jest.spyOn(process, "exit").mockImplementation(code => {});
const inputArgs = [];

parseInputParameters(inputArgs);

expect(mockProcessExit).toHaveBeenCalledWith(1);
mockProcessExit.mockRestore();
});

test("handles debug flag", () => {
Expand Down
115 changes: 82 additions & 33 deletions src/cli-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,32 @@ interface IArgs {
json: boolean;
notest: boolean;
debug: boolean;
helmTemplateOptions: string;
}

function getOptions() {
const parseInputParameters = (inputArgs): IArgs => {
const scriptName = "helm snyk";
const usageMsg = "Usage: $0 <command>";
const testCommandUsage = "test <chart-directory> [options]";
const testCommandDescription = "Check images in your charts for vulnerabilities";
const scriptExample = "$0 test . --output=snyk-out.json";
const argv = yargs(inputArgs)
.version()
.scriptName(scriptName)
.usage(usageMsg)
.command(testCommandUsage, testCommandDescription)
.help("help")
.alias("help", "h")
.options(optionsConfiguration())
.hide("notest")
.demandCommand(2)
.check(isValidChartDirectory)
.example(scriptExample).argv;

return parseOptions(argv);
};

const optionsConfiguration = () => {
return {
output: {
type: "string"
Expand All @@ -22,58 +45,84 @@ function getOptions() {
},
notest: {
type: "boolean"
},
set: {
type: "string",
describe:
"(helm template option) --set='stringArray' set values on the command line (can specify multiple or separate values with commas: --set='key1=val1,key2=val2')"
},
setFile: {
type: "string",
describe:
"(helm template option) --set='stringArray' set values on the command line (can specify multiple or separate values with commas: --set-file='key1=val1,key2=val2')"
},
setString: {
type: "string",
describe:
"(helm template option) --set-string='stringArray' set STRING values on the command line (can specify multiple or separate values with commas: --set-string='key1=val1,key2=val2')"
},
values: {
type: "string",
describe: "(helm template option) --values='file' specify values in a YAML file (can specify multiple --values='file1,file2')"
}
};
}
};

const isValidChartDirectory = argv => {
if (fs.existsSync(`${argv.chartDirectory}/Chart.yaml`) || fs.existsSync(`${argv.chartDirectory}/Chart.yml`)) return true;

const msgError = `Invalid Chart directory. ${argv.chartDirectory} is not a valid path for a Chart!`;
throw new Error(msgError);
};

function parseInputParameters(inputArgs): IArgs {
const returnObj = {
const parseOptions = (argv: any) => {
const options = {
inputDirectory: "",
output: "",
json: false,
notest: false,
debug: false
debug: false,
helmTemplateOptions: ""
} as IArgs;

const argv = yargs(inputArgs)
.version()
.scriptName("helm snyk")
.usage("Usage: $0 <command>")
.command("test <chart-directory> [options]", "Check images in your charts for vulnerabilities")
.help("help")
.alias("help", "h")
.options(getOptions())
.hide("notest")
.demandCommand(2)
.check(isValidChartDirectory)
.example("$0 test . --output=snyk-out.json").argv;

returnObj.inputDirectory = argv.chartDirectory;

options.inputDirectory = argv.chartDirectory;
if (argv.json) {
returnObj.json = argv.json;
options.json = argv.json;
}

if (argv.output) {
returnObj.output = argv.output;
options.output = argv.output;
}

if (argv.notest) {
returnObj.notest = argv.notest;
options.notest = argv.notest;
}

if (argv.debug) {
returnObj.debug = argv.debug;
options.debug = argv.debug;
}
options.helmTemplateOptions = parseHelmTemplateOptions(argv);

return returnObj;
}
return options;
};

const isValidChartDirectory = argv => {
if (fs.existsSync(`${argv.chartDirectory}/Chart.yaml`) || fs.existsSync(`${argv.chartDirectory}/Chart.yml`)) return true;
const parseHelmTemplateOptions = (argv: any) => {
const helmTemplateOptions: string[] = [];

const msgError = `Invalid Chart directory. ${argv.chartDirectory} is not a valid path for a Chart!`;
throw new Error(msgError);
if (argv.set) {
helmTemplateOptions.push(`--set ${argv.set}`);
}
if (argv.setFile) {
helmTemplateOptions.push(`--set-file ${argv.setFile}`);
}
if (argv.setString) {
helmTemplateOptions.push(`--set-string ${argv.setString}`);
}
if (argv.values) {
const values: string[] = [];
for (const value of argv.values.split(",")) {
values.push(`--values ${value}`);
}
helmTemplateOptions.push(values.join(" "));
}
return helmTemplateOptions.join(" ");
};

export { IArgs, parseInputParameters };

0 comments on commit 2eaee7e

Please sign in to comment.