import tl = require('azure-pipelines-task-lib/task'); import axios, { Method, AxiosInstance } from 'axios'; import https = require('https'); class Params { waitForEventType: string = ''; timeout: number = 3; keptnContextVar: string = ''; keptnApiEndpoint: string = ''; keptnApiToken: string = ''; keptnBridgeEndpoint: string | undefined; markBuildOn: {[index:string]:string} = { "fail": 'WARNING', "warning": 'WARNING', "info": 'NOTHING' } } const logIssueMap:{[index:string]:tl.IssueType} = { "WARNING":tl.IssueType.Warning, "FAILED":tl.IssueType.Error } const completeTaskMap:{[index:string]:tl.TaskResult} = { "WARNING":tl.TaskResult.SucceededWithIssues, "FAILED":tl.TaskResult.Failed } /** * Prepare input parameters */ function prepare():Params | undefined { try { const waitForEventType: string | undefined = tl.getInput('waitForEventType'); const project: string | undefined = tl.getInput('project'); const service: string | undefined = tl.getInput('service'); const stage: string | undefined = tl.getInput('stage'); let keptnApiEndpointConn: string | undefined = tl.getInput('keptnApiEndpoint'); let p = new Params(); let badInput:string[]=[]; if (waitForEventType !== undefined) { p.waitForEventType = waitForEventType; } else{ badInput.push('waitForEventType'); } let timeoutStr: string | undefined = tl.getInput('timeout'); if (timeoutStr != undefined){ p.timeout = +timeoutStr; } else{ badInput.push('timeout'); } let keptnContextVar: string | undefined = tl.getInput('keptnContextVar'); if (keptnContextVar != undefined){ p.keptnContextVar = keptnContextVar; } else{ badInput.push('keptnContextVar'); } let markBuildOnFail: string | undefined = tl.getInput('markBuildOnError'); if (markBuildOnFail != undefined){ p.markBuildOn.fail = markBuildOnFail; } let markBuildOnWarning: string | undefined = tl.getInput('markBuildOnWarning'); if (markBuildOnWarning != undefined){ p.markBuildOn.warning = markBuildOnWarning; } if (keptnApiEndpointConn !== undefined) { const keptnApiEndpoint: string | undefined = tl.getEndpointUrl(keptnApiEndpointConn, false); const keptnApiToken: string | undefined = tl.getEndpointAuthorizationParameter(keptnApiEndpointConn, 'apitoken', false); const keptnBridgeEndpoint: string | undefined = tl.getInput('bridgeURL'); if (keptnApiEndpoint != undefined){ p.keptnApiEndpoint = keptnApiEndpoint; } else{ badInput.push('keptnApiEndpoint'); } if (keptnApiToken !== undefined) { p.keptnApiToken = keptnApiToken; } else{ badInput.push('keptnApiToken'); } if (keptnBridgeEndpoint !== undefined) { p.keptnBridgeEndpoint = keptnBridgeEndpoint; } } else{ badInput.push('keptnApiEndpoint'); } if (badInput.length > 0) { tl.setResult(tl.TaskResult.Failed, 'missing required input (' + badInput.join(',') + ')'); return; } console.log('using keptnApiEndpoint', p.keptnApiEndpoint); console.log('using waitForEventType', p.waitForEventType); return p; } catch (err) { failTaskWithError(err); return undefined; } } /** * Main logic based on the different event types. * * @param input Parameters */ async function run(input: Params) { try { const axiosInstance = axios.create({ httpsAgent: new https.Agent({ rejectUnauthorized: false, }), }); if (input.waitForEventType == "evaluationDone") { return waitForEvaluationDone(input, axiosInstance); } else { throw new Error("Unsupported eventType"); } } catch (err) { throw err; } } /** * Request the evaluation-done event based on the startEvaluationKeptnContext task variable. * Try a couple of times since it can take a few seconds for keptn to evaluate. * * @param input Parameters * @param httpClient an instance of axios */ async function waitForEvaluationDone(input: Params, httpClient: AxiosInstance) { let keptnContext = tl.getVariable(input.keptnContextVar); console.log("using keptnContext = " + keptnContext); let evaluationScore = -1; let evaluationResult = "empty"; let options: any = { method: <Method>"GET", headers: { "x-token": input.keptnApiToken }, url: input.keptnApiEndpoint + "/mongodb-datastore/event?type=sh.keptn.event.evaluation.finished&keptnContext=" + keptnContext, }; let c = 0; let max = (input.timeout * 60) / 10; let out; console.log("waiting in steps of 10 seconds, max " + max + " loops."); do { await delay(10000); //wait 10 seconds var response = await httpClient(options); if (response.data.events != undefined && response.data.totalCount == 1) { out = response.data.events[0]; evaluationScore = out.data.evaluation.score; evaluationResult = out.data.evaluation.result; } else { if (++c > max) { evaluationResult = "not-found"; } else { console.log("wait another 10 seconds"); } } } while (evaluationResult == "empty"); handleEvaluationResult( evaluationResult, evaluationScore, keptnContext, input ); console.log("************* Result from Keptn ****************"); console.log(JSON.stringify(out, null, 2)); return evaluationResult; } function handleEvaluationResult(evaluationResult:string, evaluationScore:number, keptnContext:string|undefined, input:Params){ console.log("evaluationResult = " + evaluationResult); if (evaluationResult == "not-found"){ tl.setResult(tl.TaskResult.Failed, "No Keptn sh.keptn.events.evaluation-done event found for context"); return "No Keptn sh.keptn.events.evaluation-done event found for context"; } else if (evaluationResult == "pass"){ tl.setResult(tl.TaskResult.Succeeded, "Keptn evaluation went well. Score = " + evaluationScore); } else{ let message = "Keptn evaluation " + evaluationResult + ". Score = " + evaluationScore; let markBuild = input.markBuildOn[evaluationResult]; console.log("markBuild = " + markBuild); if (markBuild == 'NOTHING'){ console.log(message); } else{ tl.logIssue(logIssueMap[markBuild], message); tl.setResult(completeTaskMap[markBuild], message); } } if (input.keptnBridgeEndpoint != undefined){ console.log("Link to Bridge: " + input.keptnBridgeEndpoint + "/trace/" + keptnContext); } } /** * Helper function to wait an amount of millis. * @param ms */ function delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } // Fails the current task with an error message and creates // a stack trace in the output log if printStack is set to true function failTaskWithError(error: Error | string | unknown, printStack: boolean = true) { let errorMessage: string; if (typeof error === "string") { errorMessage = error; } else if (error instanceof Error) { errorMessage = error.message; // Print a stack trace to the output log if (printStack) { console.error(error.stack); } } else { errorMessage = `${error}`; } tl.setResult(tl.TaskResult.Failed, errorMessage); } /** * Main */ let input:Params | undefined = prepare(); if (input !== undefined){ run(input).then(result => { console.log(result); }).catch(err => { console.error(`Catching uncaught error and aborting task!`); failTaskWithError(err) }); }