diff --git a/__tests__/run.test.ts b/__tests__/run.test.ts index db458b2f..26c87abb 100644 --- a/__tests__/run.test.ts +++ b/__tests__/run.test.ts @@ -384,7 +384,6 @@ test("utility - getLastSuccessfulRunSha() - Get Commits under different conditio if (name == errorWebRequest) return Promise.resolve(errorWebResponse); });*/ - jest.spyOn(httpClient, 'sendRequest').mockImplementation(() => Promise.resolve(lastSuccessfulRunUrlResponse)); //Invoke and assert await expect(utility.getLastSuccessfulRunSha(process.env.GITHUB_TOKEN)).resolves.not.toThrowError; diff --git a/lib/constants.js b/lib/constants.js index 421d39b0..1ec3b50f 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -41,9 +41,9 @@ function getWorkflowAnnotationsJson(lastSuccessRunSha) { + `}`; } exports.getWorkflowAnnotationsJson = getWorkflowAnnotationsJson; -function getWorkflowAnnotationKeyLabel() { +function getWorkflowAnnotationKeyLabel(workflowFilePath) { const hashKey = require("crypto").createHash("MD5") - .update(`${process.env.GITHUB_REPOSITORY}/${process.env.GITHUB_WORKFLOW}`) + .update(`${process.env.GITHUB_REPOSITORY}/${workflowFilePath}`) .digest("hex"); return `githubWorkflow_${hashKey}`; } diff --git a/lib/githubClient.js b/lib/githubClient.js index a6497faf..9a350c2f 100644 --- a/lib/githubClient.js +++ b/lib/githubClient.js @@ -17,10 +17,10 @@ class GitHubClient { this._repository = repository; this._token = token; } - getSuccessfulRunsOnBranch(branch, force) { + getSuccessfulRunsOnBranch(branch, workflowFileName, force) { return __awaiter(this, void 0, void 0, function* () { if (force || !this._successfulRunsOnBranchPromise) { - const lastSuccessfulRunUrl = `https://api.github.com/repos/${this._repository}/actions/runs?status=success&branch=${branch}`; + const lastSuccessfulRunUrl = `https://api.github.com/repos/${this._repository}/actions/workflows/${workflowFileName}/runs?status=success&branch=${branch}`; const webRequest = new httpClient_1.WebRequest(); webRequest.method = "GET"; webRequest.uri = lastSuccessfulRunUrl; @@ -34,5 +34,22 @@ class GitHubClient { return this._successfulRunsOnBranchPromise; }); } + getWorkflows(force) { + return __awaiter(this, void 0, void 0, function* () { + if (force || !this._workflowsPromise) { + const getWorkflowFileNameUrl = `https://api.github.com/repos/${this._repository}/actions/workflows`; + const webRequest = new httpClient_1.WebRequest(); + webRequest.method = "GET"; + webRequest.uri = getWorkflowFileNameUrl; + webRequest.headers = { + Authorization: `Bearer ${this._token}` + }; + core.debug(`Getting workflows for repo: ${this._repository}`); + const response = yield httpClient_1.sendRequest(webRequest); + this._workflowsPromise = Promise.resolve(response); + } + return this._workflowsPromise; + }); + } } exports.GitHubClient = GitHubClient; diff --git a/lib/utilities/strategy-helpers/deployment-helper.js b/lib/utilities/strategy-helpers/deployment-helper.js index cf9880d6..32b9d3c9 100644 --- a/lib/utilities/strategy-helpers/deployment-helper.js +++ b/lib/utilities/strategy-helpers/deployment-helper.js @@ -111,25 +111,26 @@ function checkManifestStability(kubectl, resources) { }); } function annotateAndLabelResources(files, kubectl, resourceTypes, allPods) { - const annotationKeyLabel = models.getWorkflowAnnotationKeyLabel(); - annotateResources(files, kubectl, resourceTypes, allPods, annotationKeyLabel); - labelResources(files, kubectl, annotationKeyLabel); + return __awaiter(this, void 0, void 0, function* () { + const workflowFilePath = yield utility_1.getWorkflowFilePath(TaskInputParameters.githubToken); + const annotationKeyLabel = models.getWorkflowAnnotationKeyLabel(workflowFilePath); + annotateResources(files, kubectl, resourceTypes, allPods, annotationKeyLabel); + labelResources(files, kubectl, annotationKeyLabel); + }); } function annotateResources(files, kubectl, resourceTypes, allPods, annotationKey) { - return __awaiter(this, void 0, void 0, function* () { - const annotateResults = []; - const lastSuccessSha = yield utility_1.getLastSuccessfulRunSha(TaskInputParameters.githubToken); - let annotationKeyValStr = annotationKey + '=' + models.getWorkflowAnnotationsJson(lastSuccessSha); - annotateResults.push(kubectl.annotate('namespace', TaskInputParameters.namespace, [annotationKeyValStr], true)); - annotateResults.push(kubectl.annotateFiles(files, [annotationKeyValStr], true)); - resourceTypes.forEach(resource => { - if (resource.type.toUpperCase() !== models.KubernetesWorkload.pod.toUpperCase()) { - utility_1.annotateChildPods(kubectl, resource.type, resource.name, annotationKeyValStr, allPods) - .forEach(execResult => annotateResults.push(execResult)); - } - }); - utility_1.checkForErrors(annotateResults, true); + const annotateResults = []; + const lastSuccessSha = utility_1.getLastSuccessfulRunSha(kubectl, TaskInputParameters.namespace, annotationKey); + let annotationKeyValStr = annotationKey + '=' + models.getWorkflowAnnotationsJson(lastSuccessSha); + annotateResults.push(kubectl.annotate('namespace', TaskInputParameters.namespace, [annotationKeyValStr], true)); + annotateResults.push(kubectl.annotateFiles(files, [annotationKeyValStr], true)); + resourceTypes.forEach(resource => { + if (resource.type.toUpperCase() !== models.KubernetesWorkload.pod.toUpperCase()) { + utility_1.annotateChildPods(kubectl, resource.type, resource.name, annotationKeyValStr, allPods) + .forEach(execResult => annotateResults.push(execResult)); + } }); + utility_1.checkForErrors(annotateResults, true); } function labelResources(files, kubectl, label) { utility_1.checkForErrors([kubectl.labelFiles(files, [`workflow=${label}`], true)], true); diff --git a/lib/utilities/utility.js b/lib/utilities/utility.js index 09eaee43..297942f3 100644 --- a/lib/utilities/utility.js +++ b/lib/utilities/utility.js @@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getCurrentTime = exports.getRandomInt = exports.sleep = exports.annotateChildPods = exports.getLastSuccessfulRunSha = exports.checkForErrors = exports.isEqual = exports.getExecutableExtension = void 0; +exports.getCurrentTime = exports.getRandomInt = exports.sleep = exports.annotateChildPods = exports.getWorkflowFilePath = exports.getLastSuccessfulRunSha = exports.checkForErrors = exports.isEqual = exports.getExecutableExtension = void 0; const os = require("os"); const core = require("@actions/core"); const githubClient_1 = require("../githubClient"); @@ -60,29 +60,55 @@ function checkForErrors(execResults, warnIfError) { } } exports.checkForErrors = checkForErrors; -function getLastSuccessfulRunSha(githubToken) { - return __awaiter(this, void 0, void 0, function* () { - let lastSuccessRunSha = ''; - const gitHubClient = new githubClient_1.GitHubClient(process.env.GITHUB_REPOSITORY, githubToken); - const branch = process.env.GITHUB_REF.replace("refs/heads/", ""); - const response = yield gitHubClient.getSuccessfulRunsOnBranch(branch); - if (response.statusCode == httpClient_1.StatusCodes.OK - && !!response.body - && !!response.body.total_count) { - if (response.body.total_count > 0) { - lastSuccessRunSha = response.body.workflow_runs[0].head_sha; +function getLastSuccessfulRunSha(kubectl, namespaceName, annotationKey) { + const result = kubectl.getResource('namespace', namespaceName); + if (!result) { + core.debug(`Failed to get commits from cluster.`); + return ''; + } + else { + if (result.stderr) { + core.debug(`${result.stderr}`); + return process.env.GITHUB_SHA; + } + else if (result.stdout) { + const annotationsSet = JSON.parse(result.stdout).metadata.annotations; + if (!!annotationsSet[annotationKey]) { + return JSON.parse(annotationsSet[annotationKey].replace(/'/g, '"')).commit; } else { - lastSuccessRunSha = 'NA'; + return 'NA'; } } - else if (response.statusCode != httpClient_1.StatusCodes.OK) { - core.debug(`An error occured while getting succeessful run results. Statuscode: ${response.statusCode}, StatusMessage: ${response.statusMessage}`); - } - return Promise.resolve(lastSuccessRunSha); - }); + } } exports.getLastSuccessfulRunSha = getLastSuccessfulRunSha; +function getWorkflowFilePath(githubToken) { + return __awaiter(this, void 0, void 0, function* () { + let workflowFilePath = process.env.GITHUB_WORKFLOW; + if (!workflowFilePath.startsWith('.github/workflows/')) { + const githubClient = new githubClient_1.GitHubClient(process.env.GITHUB_REPOSITORY, githubToken); + const response = yield githubClient.getWorkflows(); + if (response.statusCode == httpClient_1.StatusCodes.OK + && !!response.body + && !!response.body.total_count) { + if (response.body.total_count > 0) { + for (let workflow of response.body.workflows) { + if (process.env.GITHUB_WORKFLOW === workflow.name) { + workflowFilePath = workflow.path; + break; + } + } + } + } + else if (response.statusCode != httpClient_1.StatusCodes.OK) { + core.debug(`An error occured while getting list of workflows on the repo. Statuscode: ${response.statusCode}, StatusMessage: ${response.statusMessage}`); + } + } + return Promise.resolve(workflowFilePath); + }); +} +exports.getWorkflowFilePath = getWorkflowFilePath; function annotateChildPods(kubectl, resourceType, resourceName, annotationKeyValStr, allPods) { const commandExecutionResults = []; let owner = resourceName; diff --git a/src/constants.ts b/src/constants.ts index c9f2d3fc..b2dc7e44 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -40,9 +40,9 @@ export function getWorkflowAnnotationsJson(lastSuccessRunSha: string): string { + `'provider': 'GitHub'` + `}`; } -export function getWorkflowAnnotationKeyLabel(): string { +export function getWorkflowAnnotationKeyLabel(workflowFilePath: string): string { const hashKey = require("crypto").createHash("MD5") - .update(`${process.env.GITHUB_REPOSITORY}/${process.env.GITHUB_WORKFLOW}`) + .update(`${process.env.GITHUB_REPOSITORY}/${workflowFilePath}`) .digest("hex"); return `githubWorkflow_${hashKey}`; } \ No newline at end of file diff --git a/src/githubClient.ts b/src/githubClient.ts index 442592b6..11ad069a 100644 --- a/src/githubClient.ts +++ b/src/githubClient.ts @@ -7,9 +7,9 @@ export class GitHubClient { this._token = token; } - public async getSuccessfulRunsOnBranch(branch: string, force?: boolean): Promise { + public async getSuccessfulRunsOnBranch(branch: string, workflowFileName: string, force?: boolean): Promise { if (force || !this._successfulRunsOnBranchPromise) { - const lastSuccessfulRunUrl = `https://api.github.com/repos/${this._repository}/actions/runs?status=success&branch=${branch}`; + const lastSuccessfulRunUrl = `https://api.github.com/repos/${this._repository}/actions/workflows/${workflowFileName}/runs?status=success&branch=${branch}`; const webRequest = new WebRequest(); webRequest.method = "GET"; webRequest.uri = lastSuccessfulRunUrl; @@ -24,7 +24,25 @@ export class GitHubClient { return this._successfulRunsOnBranchPromise; } + public async getWorkflows(force?: boolean): Promise { + if (force || !this._workflowsPromise) { + const getWorkflowFileNameUrl = `https://api.github.com/repos/${this._repository}/actions/workflows`; + const webRequest = new WebRequest(); + webRequest.method = "GET"; + webRequest.uri = getWorkflowFileNameUrl; + webRequest.headers = { + Authorization: `Bearer ${this._token}` + }; + + core.debug(`Getting workflows for repo: ${this._repository}`); + const response: WebResponse = await sendRequest(webRequest); + this._workflowsPromise = Promise.resolve(response); + } + return this._workflowsPromise; + } + private _repository: string; private _token: string; private _successfulRunsOnBranchPromise: Promise; + private _workflowsPromise: Promise; } \ No newline at end of file diff --git a/src/utilities/strategy-helpers/deployment-helper.ts b/src/utilities/strategy-helpers/deployment-helper.ts index 04613cfc..31314d94 100644 --- a/src/utilities/strategy-helpers/deployment-helper.ts +++ b/src/utilities/strategy-helpers/deployment-helper.ts @@ -17,7 +17,7 @@ import { IExecSyncResult } from '../../utilities/tool-runner'; import { deployPodCanary } from './pod-canary-deployment-helper'; import { deploySMICanary } from './smi-canary-deployment-helper'; -import { checkForErrors, annotateChildPods, getLastSuccessfulRunSha } from "../utility"; +import { checkForErrors, annotateChildPods, getWorkflowFilePath, getLastSuccessfulRunSha } from "../utility"; export async function deploy(kubectl: Kubectl, manifestFilePaths: string[], deploymentStrategy: string) { @@ -112,15 +112,16 @@ async function checkManifestStability(kubectl: Kubectl, resources: Resource[]): await KubernetesManifestUtility.checkManifestStability(kubectl, resources); } -function annotateAndLabelResources(files: string[], kubectl: Kubectl, resourceTypes: Resource[], allPods: any) { - const annotationKeyLabel = models.getWorkflowAnnotationKeyLabel(); +async function annotateAndLabelResources(files: string[], kubectl: Kubectl, resourceTypes: Resource[], allPods: any) { + const workflowFilePath = await getWorkflowFilePath(TaskInputParameters.githubToken); + const annotationKeyLabel = models.getWorkflowAnnotationKeyLabel(workflowFilePath); annotateResources(files, kubectl, resourceTypes, allPods, annotationKeyLabel); labelResources(files, kubectl, annotationKeyLabel); } -async function annotateResources(files: string[], kubectl: Kubectl, resourceTypes: Resource[], allPods: any, annotationKey: string) { +function annotateResources(files: string[], kubectl: Kubectl, resourceTypes: Resource[], allPods: any, annotationKey: string) { const annotateResults: IExecSyncResult[] = []; - const lastSuccessSha = await getLastSuccessfulRunSha(TaskInputParameters.githubToken); + const lastSuccessSha = getLastSuccessfulRunSha(kubectl, TaskInputParameters.namespace, annotationKey); let annotationKeyValStr = annotationKey + '=' + models.getWorkflowAnnotationsJson(lastSuccessSha); annotateResults.push(kubectl.annotate('namespace', TaskInputParameters.namespace, [annotationKeyValStr], true)); annotateResults.push(kubectl.annotateFiles(files, [annotationKeyValStr], true)); diff --git a/src/utilities/utility.ts b/src/utilities/utility.ts index c506e7a0..c1588b23 100644 --- a/src/utilities/utility.ts +++ b/src/utilities/utility.ts @@ -51,25 +51,51 @@ export function checkForErrors(execResults: IExecSyncResult[], warnIfError?: boo } } -export async function getLastSuccessfulRunSha(githubToken: string): Promise { - let lastSuccessRunSha = ''; - const gitHubClient = new GitHubClient(process.env.GITHUB_REPOSITORY, githubToken); - const branch = process.env.GITHUB_REF.replace("refs/heads/", ""); - const response = await gitHubClient.getSuccessfulRunsOnBranch(branch); - if (response.statusCode == StatusCodes.OK - && !!response.body - && !!response.body.total_count) { - if (response.body.total_count > 0) { - lastSuccessRunSha = response.body.workflow_runs[0].head_sha; +export function getLastSuccessfulRunSha(kubectl: Kubectl, namespaceName: string, annotationKey: string): string { + const result = kubectl.getResource('namespace', namespaceName); + if (!result) { + core.debug(`Failed to get commits from cluster.`); + return ''; + } + else { + if (result.stderr) { + core.debug(`${result.stderr}`); + return process.env.GITHUB_SHA; } - else { - lastSuccessRunSha = 'NA'; + else if (result.stdout) { + const annotationsSet = JSON.parse(result.stdout).metadata.annotations; + if (!!annotationsSet[annotationKey]) { + return JSON.parse(annotationsSet[annotationKey].replace(/'/g, '"')).commit; + } + else { + return 'NA'; + } } } - else if (response.statusCode != StatusCodes.OK) { - core.debug(`An error occured while getting succeessful run results. Statuscode: ${response.statusCode}, StatusMessage: ${response.statusMessage}`); +} + +export async function getWorkflowFilePath(githubToken: string): Promise { + let workflowFilePath = process.env.GITHUB_WORKFLOW; + if (!workflowFilePath.startsWith('.github/workflows/')) { + const githubClient = new GitHubClient(process.env.GITHUB_REPOSITORY, githubToken); + const response = await githubClient.getWorkflows(); + if (response.statusCode == StatusCodes.OK + && !!response.body + && !!response.body.total_count) { + if (response.body.total_count > 0) { + for (let workflow of response.body.workflows) { + if (process.env.GITHUB_WORKFLOW === workflow.name) { + workflowFilePath = workflow.path; + break; + } + } + } + } + else if (response.statusCode != StatusCodes.OK) { + core.debug(`An error occured while getting list of workflows on the repo. Statuscode: ${response.statusCode}, StatusMessage: ${response.statusMessage}`); + } } - return Promise.resolve(lastSuccessRunSha); + return Promise.resolve(workflowFilePath); } export function annotateChildPods(kubectl: Kubectl, resourceType: string, resourceName: string, annotationKeyValStr: string, allPods): IExecSyncResult[] {