Skip to content

Commit

Permalink
Merge pull request #117 from snyk/develop
Browse files Browse the repository at this point in the history
Merge develop into master for release
  • Loading branch information
maxjeffos authored Jun 10, 2021
2 parents 3bcb84c + d24bde2 commit 8daad02
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 91 deletions.
49 changes: 49 additions & 0 deletions snykTask/src/__tests__/install/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { getSnykDownloadInfo, downloadExecutable } from "../../install";
import { Platform } from "azure-pipelines-task-lib/task";

describe("getSnykDownloadInfo", () => {
it("retrieves the correct download info for Linux", () => {
const dlInfo = getSnykDownloadInfo(Platform.Linux);
expect(dlInfo).toEqual({
snyk: {
filename: "snyk-linux",
downloadUrl: "https://static.snyk.io/cli/latest/snyk-linux"
},
snykToHtml: {
filename: "snyk-to-html-linux",
downloadUrl:
"https://static.snyk.io/snyk-to-html/latest/snyk-to-html-linux"
}
});
});

it("retrieves the correct download info for Windows", () => {
const dlInfo = getSnykDownloadInfo(Platform.Windows);
expect(dlInfo).toEqual({
snyk: {
filename: "snyk-win.exe",
downloadUrl: "https://static.snyk.io/cli/latest/snyk-win.exe"
},
snykToHtml: {
filename: "snyk-to-html-win.exe",
downloadUrl:
"https://static.snyk.io/snyk-to-html/latest/snyk-to-html-win.exe"
}
});
});

it("retrieves the correct download info for MacOS", () => {
const dlInfo = getSnykDownloadInfo(Platform.MacOS);
expect(dlInfo).toEqual({
snyk: {
filename: "snyk-macos",
downloadUrl: "https://static.snyk.io/cli/latest/snyk-macos"
},
snykToHtml: {
filename: "snyk-to-html-macos",
downloadUrl:
"https://static.snyk.io/snyk-to-html/latest/snyk-to-html-macos"
}
});
});
});
4 changes: 3 additions & 1 deletion snykTask/src/__tests__/task-lib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,16 @@ test("getOptionsToExecuteSnykCLICommand builds IExecOptions like we need it", ()
const options: tr.IExecOptions = getOptionsToExecuteSnykCLICommand(
taskArgs,
taskNameForAnalytics,
version
version,
"fake-token"
);

expect(options.cwd).toBe("/some/path");
expect(options.failOnStdErr).toBe(false);
expect(options.ignoreReturnCode).toBe(true);
expect(options.env?.SNYK_INTEGRATION_NAME).toBe("AZURE_PIPELINES");
expect(options.env?.SNYK_INTEGRATION_VERSION).toBe(version);
expect(options.env?.SNYK_TOKEN).toBe("fake-token");
});

test("getOptionsForSnykToHtml builds IExecOptions for running snyk-to-html", () => {
Expand Down
143 changes: 60 additions & 83 deletions snykTask/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from "./task-lib";
import * as fs from "fs";
import * as path from "path";
import { getSnykDownloadInfo, downloadExecutable } from "./install";

class SnykError extends Error {
constructor(message?: string) {
Expand Down Expand Up @@ -49,19 +50,6 @@ if (isDebugMode()) {
console.log(`taskNameForAnalytics: ${taskNameForAnalytics}`);
}

function buildToolRunner(
tool: string,
requiresSudo: boolean = false
): tr.ToolRunner {
const toolPath: string = getToolPath(tool, tl.which, requiresSudo);
let toolRunner = tl.tool(toolPath);

if (requiresSudo) toolRunner = toolRunner.arg(tool);
if (isDebugMode()) console.log(`toolPath: ${toolPath}`);

return toolRunner;
}

async function sleep(seconds: number): Promise<void> {
const ms = seconds * 1000;
return new Promise(resolve => setTimeout(resolve, ms));
Expand Down Expand Up @@ -127,61 +115,18 @@ async function showDirectoryListing(
await lsToolRunner.exec(options);
}

async function installSnyk(
taskArgs: TaskArgs,
useSudo: boolean
): Promise<SnykOutput> {
const options = getOptionsToExecuteCmd(taskArgs);
const installSnykToolRunner: tr.ToolRunner = buildToolRunner("npm", useSudo)
.arg("install")
.arg("-g")
.arg("snyk")
.arg("snyk-to-html");
const installSnykExitCode = await installSnykToolRunner.exec(options);
if (isDebugMode())
console.log(`installSnykExitCode: ${installSnykExitCode}\n`);
const snykOutput: SnykOutput = {
code: installSnykExitCode,
message: "Not possible to install snyk and snyk-to-html packages"
};

return snykOutput;
}

async function authorizeSnyk(
taskArgs: TaskArgs,
snykToken: string
): Promise<SnykOutput> {
// TODO: play with setVariable as an option to use instead of running `snyk auth`
// tl.setVariable('SNYK_TOKEN', authToken, true);
const options = getOptionsToExecuteSnykCLICommand(
taskArgs,
taskNameForAnalytics,
taskVersion
);
const snykAuthToolRunner: tr.ToolRunner = buildToolRunner("snyk")
.arg("auth")
.arg(snykToken)
.argIf(taskArgs.ignoreUnknownCA, `--insecure`);
const snykAuthExitCode = await snykAuthToolRunner.exec(options);
if (isDebugMode()) console.log(`snykAuthExitCode: ${snykAuthExitCode}\n`);
const snykOutput: SnykOutput = {
code: snykAuthExitCode,
message:
"Invalid token - Snyk cannot authorize the given token. Make sure you correctly configure a Service Connection of type `Snyk Authentication` at the project level."
};

return snykOutput;
}

async function runSnykTest(
snykPath: string,
taskArgs: TaskArgs,
jsonReportOutputPath: string
jsonReportOutputPath: string,
snykToken: string
): Promise<SnykOutput> {
let errorMsg = "";
let code = 0;
const fileArg = taskArgs.getFileParameter();
const snykTestToolRunner: tr.ToolRunner = buildToolRunner("snyk")

const snykTestToolRunner = tl
.tool(snykPath)
.arg("test")
.argIf(
taskArgs.severityThreshold,
Expand All @@ -197,7 +142,8 @@ async function runSnykTest(
const options = getOptionsToExecuteSnykCLICommand(
taskArgs,
taskNameForAnalytics,
taskVersion
taskVersion,
snykToken
);

const command = `[command]${getToolPath("snyk", tl.which)} snyk test...`;
Expand Down Expand Up @@ -226,6 +172,7 @@ async function runSnykTest(
}

const runSnykToHTML = async (
snykToHtmlPath: string,
taskArgs: TaskArgs,
jsonReportFullPath: string,
htmlReportFileFullPath: string
Expand All @@ -244,7 +191,8 @@ const runSnykToHTML = async (
)} -i ${jsonReportFullPath}`;
console.log(command);

const snykToHTMLToolRunner: tr.ToolRunner = buildToolRunner("snyk-to-html")
const snykToHTMLToolRunner = tl
.tool(snykToHtmlPath)
.arg("-i")
.arg(jsonReportFullPath);
const snykToHTMLExitCode = await snykToHTMLToolRunner.exec(
Expand All @@ -265,15 +213,21 @@ const runSnykToHTML = async (
return snykOutput;
};

async function runSnykMonitor(taskArgs: TaskArgs): Promise<SnykOutput> {
async function runSnykMonitor(
snykPath: string,
taskArgs: TaskArgs,
snykToken
): Promise<SnykOutput> {
let errorMsg = "";
const fileArg = taskArgs.getFileParameter();
const options = getOptionsToExecuteSnykCLICommand(
taskArgs,
taskNameForAnalytics,
taskVersion
taskVersion,
snykToken
);
const snykMonitorToolRunner: tr.ToolRunner = buildToolRunner("snyk")
const snykMonitorToolRunner = tl
.tool(snykPath)
.arg("monitor")
.argIf(taskArgs.dockerImageName, `--docker`)
.argIf(taskArgs.dockerImageName, `${taskArgs.dockerImageName}`)
Expand Down Expand Up @@ -332,16 +286,6 @@ const handleSnykMonitorError = snykMonitorResult => {
throw new SnykError(snykMonitorResult.message);
};

const handleSnykAuthError = authorizeSnykResult => {
if (authorizeSnykResult.code !== CLI_EXIT_CODE_SUCCESS)
throw new SnykError(authorizeSnykResult.message);
};

const handleSnykInstallError = installSnykResult => {
if (installSnykResult.code !== CLI_EXIT_CODE_SUCCESS)
throw new SnykError(installSnykResult.message);
};

async function run() {
try {
const currentDir: string = tl.cwd(); // Azure mock framework will return empty string / undefined
Expand Down Expand Up @@ -381,8 +325,8 @@ async function run() {
}

const taskArgs: TaskArgs = parseInputArgs();
const authTokenToUse = getAuthToken();
if (!authTokenToUse) {
const snykToken = getAuthToken();
if (!snykToken) {
const errorMsg =
"auth token is not set. Setup SnykAuth service connection and specify serviceConnectionEndpoint input parameter.";
throw new SnykError(errorMsg);
Expand All @@ -403,10 +347,38 @@ async function run() {
}
if (isDebugMode()) console.log(`useSudo: ${useSudo}`);

handleSnykInstallError(await installSnyk(taskArgs, useSudo));
handleSnykAuthError(await authorizeSnyk(taskArgs, authTokenToUse));
const agentTempDirectory = tl.getVariable("Agent.TempDirectory");
if (!agentTempDirectory) {
throw new Error("Agent.TempDirectory is not set"); // should always be set by Azure Pipelines environment
}
const snykToolDownloads = getSnykDownloadInfo(platform);
await downloadExecutable(agentTempDirectory, snykToolDownloads.snyk);
await downloadExecutable(agentTempDirectory, snykToolDownloads.snykToHtml);
const snykPath = path.resolve(
agentTempDirectory,
snykToolDownloads.snyk.filename
);
const snykToHtmlPath = path.resolve(
agentTempDirectory,
snykToolDownloads.snykToHtml.filename
);

if (isDebugMode()) {
console.log("snykPath: " + snykPath);
console.log("snykToHtmlPath: " + snykToHtmlPath);
console.log("showing contents of agent temp directory...");
await showDirectoryListing(
getOptionsToExecuteCmd(taskArgs),
reportOutputDir
);
}

const snykTestResult = await runSnykTest(taskArgs, jsonReportFullPath);
const snykTestResult = await runSnykTest(
snykPath,
taskArgs,
jsonReportFullPath,
snykToken
);

if (taskArgs.delayAfterReportGenerationSeconds > 0) {
console.log(
Expand All @@ -419,6 +391,7 @@ async function run() {
}

const snykToHTMLResult = await runSnykToHTML(
snykToHtmlPath,
taskArgs,
jsonReportFullPath,
htmlReportFullPath
Expand Down Expand Up @@ -456,7 +429,11 @@ async function run() {
);

if (taskArgs.monitorOnBuild) {
const snykMonitorResult = await runSnykMonitor(taskArgs);
const snykMonitorResult = await runSnykMonitor(
snykPath,
taskArgs,
snykToken
);
handleSnykMonitorError(snykMonitorResult);
}

Expand Down
54 changes: 54 additions & 0 deletions snykTask/src/install/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Platform } from "azure-pipelines-task-lib/task";
import * as fs from "fs";
import * as path from "path";
import * as https from "https";

export type Executable = {
filename: string;
downloadUrl: string;
};

export type SnykDownloads = {
snyk: Executable;
snykToHtml: Executable;
};

export function getSnykDownloadInfo(platform: Platform): SnykDownloads {
const baseUrl = "https://static.snyk.io";

const filenameSuffixes: Record<Platform, string> = {
[Platform.Linux]: "linux",
[Platform.Windows]: "win.exe",
[Platform.MacOS]: "macos"
};

return {
snyk: {
filename: `snyk-${filenameSuffixes[platform]}`,
downloadUrl: `${baseUrl}/cli/latest/snyk-${filenameSuffixes[platform]}`
},
snykToHtml: {
filename: `snyk-to-html-${filenameSuffixes[platform]}`,
downloadUrl: `${baseUrl}/snyk-to-html/latest/snyk-to-html-${filenameSuffixes[platform]}`
}
};
}

export async function downloadExecutable(
targetDirectory: string,
executable: Executable
) {
const fileWriter = fs.createWriteStream(
path.join(targetDirectory, executable.filename),
{
mode: 0o766
}
);
return new Promise<void>((resolve, reject) => {
https.get(executable.downloadUrl, response => {
response.on("end", () => resolve());
response.on("error", err => reject(err));
response.pipe(fileWriter);
});
});
}
15 changes: 8 additions & 7 deletions snykTask/src/task-lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,20 @@ export const getOptionsToExecuteCmd = (taskArgs: TaskArgs): tr.IExecOptions => {
export const getOptionsToExecuteSnykCLICommand = (
taskArgs: TaskArgs,
taskNameForAnalytics: string,
taskVersion: string
taskVersion: string,
snykToken: string
): tr.IExecOptions => {
const envVars = process.env;
envVars["SNYK_INTEGRATION_NAME"] = taskNameForAnalytics;
envVars["SNYK_INTEGRATION_VERSION"] = taskVersion;

const options = {
cwd: taskArgs.testDirectory,
failOnStdErr: false,
ignoreReturnCode: true,
env: envVars
env: {
...process.env,
SNYK_INTEGRATION_NAME: taskNameForAnalytics,
SNYK_INTEGRATION_VERSION: taskVersion,
SNYK_TOKEN: snykToken
} as tr.IExecOptions["env"]
} as tr.IExecOptions;

return options;
};

Expand Down

0 comments on commit 8daad02

Please sign in to comment.