From 04921d7d0660698f7a4790978b04a96611f441d0 Mon Sep 17 00:00:00 2001 From: Jyotsna Date: Wed, 30 Dec 2020 15:03:39 +0530 Subject: [PATCH] New traceability fields added to annotations (#90) * New traceability fields --- __tests__/run.test.ts | 16 +++-- lib/constants.js | 33 +++++---- lib/docker-object-model.js | 31 ++++++++ .../strategy-helpers/deployment-helper.js | 7 +- lib/utilities/utility.js | 64 ++++++++++++++++- src/constants.ts | 35 +++++---- src/docker-object-model.ts | 33 +++++++++ .../strategy-helpers/deployment-helper.ts | 13 ++-- src/utilities/utility.ts | 71 +++++++++++++++++++ 9 files changed, 257 insertions(+), 46 deletions(-) create mode 100644 lib/docker-object-model.js create mode 100644 src/docker-object-model.ts diff --git a/__tests__/run.test.ts b/__tests__/run.test.ts index 9c05ab93..766487ea 100644 --- a/__tests__/run.test.ts +++ b/__tests__/run.test.ts @@ -38,7 +38,7 @@ const getAllPodsMock = { const getNamespaceMock = { 'code': 0, - 'stdout': '{"apiVersion": "v1","kind": "Namespace","metadata": {"annotations": {"githubWorkflow_c11401b9d232942bac19cbc5bc32b42d": "{\'run\': \'202489005\',\'repository\': \'testUser/hello-kubernetes\',\'workflow\': \'workflow1\',\'jobName\': \'build-and-deploy\',\'createdBy\': \'testUser\',\'runUri\': \'https://github.com/testUser/hello-kubernetes/actions/runs/202489005\',\'commit\': \'currentCommit\',\'lastSuccessRunCommit\': \'lastCommit\',\'branch\': \'refs/heads/branch-rename\',\'deployTimestamp\': \'1597062957973\',\'provider\': \'GitHub\'}","githubWorkflow_21fd7a597282ca5adc05ba99018b3706": "{\'run\': \'202504411\',\'repository\': \'testUser/hello-kubernetes\',\'workflow\': \'workflowMaster\',\'jobName\': \'build-and-deploy\',\'createdBy\': \'testUser\',\'runUri\': \'https://github.com/testUser/hello-kubernetes/actions/runs/202504411\',\'commit\': \'currentCommit1\',\'lastSuccessRunCommit\': \'NA\',\'branch\': \'refs/heads/master\',\'deployTimestamp\': \'1597063919873\',\'provider\': \'GitHub\'}"}},"spec": {"finalizers": ["kubernetes"]},"status": {"phase": "Active"}}' + 'stdout': '{"apiVersion": "v1","kind": "Namespace","metadata": {"annotations": {"githubWorkflow_c11401b9d232942bac19cbc5bc32b42d": "{\'run\': \'202489005\',\'repository\': \'testUser/hello-kubernetes\',\'workflow\': \'workflow1\',\'jobName\': \'build-and-deploy\',\'createdBy\': \'testUser\',\'runUri\': \'https://github.com/testUser/hello-kubernetes/actions/runs/202489005\',\'commit\': \'currentCommit\',\'lastSuccessRunCommit\': \'lastCommit\',\'branch\': \'refs/heads/branch-rename\',\'deployTimestamp\': \'1597062957973\',\'dockerfilePaths\': \'{}\',\'manifestsPaths\': \'[]\',\'helmChartPaths\': \'[]\',\'provider\': \'GitHub\'}","githubWorkflow_21fd7a597282ca5adc05ba99018b3706": "{\'run\': \'202504411\',\'repository\': \'testUser/hello-kubernetes\',\'workflow\': \'workflowMaster\',\'jobName\': \'build-and-deploy\',\'createdBy\': \'testUser\',\'runUri\': \'https://github.com/testUser/hello-kubernetes/actions/runs/202504411\',\'commit\': \'currentCommit1\',\'lastSuccessRunCommit\': \'NA\',\'branch\': \'refs/heads/master\',\'deployTimestamp\': \'1597063919873\',\'dockerfilePaths\': \'{}\',\'manifestsPaths\': \'[]\',\'helmChartPaths\': \'[]\',\'provider\': \'GitHub\'}"}},"spec": {"finalizers": ["kubernetes"]},"status": {"phase": "Active"}}' }; const getWorkflowsUrlResponse = { @@ -79,7 +79,7 @@ const resources: Resource[] = [{ type: "Deployment", name: "AppName" }]; beforeEach(() => { deploymentYaml = fs.readFileSync(path.join(__dirname, 'manifests', 'deployment.yml'), 'utf8'); jest.spyOn(Date, 'now').mockImplementation(() => 1234561234567); - + process.env["KUBECONFIG"] = 'kubeConfig'; process.env['GITHUB_RUN_ID'] = '12345'; process.env['GITHUB_WORKFLOW'] = '.github/workflows/workflow.yml'; @@ -293,7 +293,8 @@ test("deployment - deploy() - deploy force flag on", async () => { }); test("deployment - deploy() - Annotate & label resources", async () => { - let annotationKeyValStr = getWorkflowAnnotationKeyLabel(process.env.GITHUB_WORKFLOW) + '=' + getWorkflowAnnotationsJson('currentCommit', '.github/workflows/workflow.yml'); + let deploymentConfig: utility.DeploymentConfig = { manifestFilePaths: ['manifests/deployment.yaml'], helmChartFilePaths: [], dockerfilePaths: {} }; + let annotationKeyValStr = getWorkflowAnnotationKeyLabel(process.env.GITHUB_WORKFLOW) + '=' + getWorkflowAnnotationsJson('currentCommit', '.github/workflows/workflow.yml', deploymentConfig); const KubernetesManifestUtilityMock = mocked(KubernetesManifestUtility, true); KubernetesManifestUtilityMock.checkManifestStability = jest.fn().mockReturnValue(""); const KubernetesObjectUtilityMock = mocked(KubernetesObjectUtility, true); @@ -301,8 +302,9 @@ test("deployment - deploy() - Annotate & label resources", async () => { const fileHelperMock = mocked(fileHelper, true); const fsMock = (mocked(fs, true)); fileHelperMock.getTempDirectory = jest.fn().mockReturnValue("~/Deployment_testapp_currentTimestamp"); - fsMock.writeFileSync =jest.fn().mockReturnValue(""); + fsMock.writeFileSync = jest.fn().mockReturnValue(""); jest.spyOn(utility, 'getWorkflowFilePath').mockImplementation(() => Promise.resolve(process.env.GITHUB_WORKFLOW)); + jest.spyOn(utility, 'getDeploymentConfig').mockImplementation(() => Promise.resolve(deploymentConfig)); const kubeCtl: jest.Mocked = new Kubectl("") as any; kubeCtl.apply = jest.fn().mockReturnValue(""); @@ -323,7 +325,8 @@ test("deployment - deploy() - Annotate & label resources", async () => { test("deployment - deploy() - Annotate & label resources for a new workflow", async () => { process.env.GITHUB_WORKFLOW = '.github/workflows/NewWorkflow.yml'; - let annotationKeyValStr = getWorkflowAnnotationKeyLabel(process.env.GITHUB_WORKFLOW) + '=' + getWorkflowAnnotationsJson('NA', '.github/workflows/NewWorkflow.yml'); + let deploymentConfig: utility.DeploymentConfig = { manifestFilePaths: ['manifests/deployment.yaml'], helmChartFilePaths: [], dockerfilePaths: {} } + let annotationKeyValStr = getWorkflowAnnotationKeyLabel(process.env.GITHUB_WORKFLOW) + '=' + getWorkflowAnnotationsJson('NA', '.github/workflows/NewWorkflow.yml', deploymentConfig); const KubernetesManifestUtilityMock = mocked(KubernetesManifestUtility, true); KubernetesManifestUtilityMock.checkManifestStability = jest.fn().mockReturnValue(""); const KubernetesObjectUtilityMock = mocked(KubernetesObjectUtility, true); @@ -331,8 +334,9 @@ test("deployment - deploy() - Annotate & label resources for a new workflow", as const fileHelperMock = mocked(fileHelper, true); const fsMock = (mocked(fs, true)); fileHelperMock.getTempDirectory = jest.fn().mockReturnValue("~/Deployment_testapp_currentTimestamp"); - fsMock.writeFileSync =jest.fn().mockReturnValue(""); + fsMock.writeFileSync = jest.fn().mockReturnValue(""); jest.spyOn(httpClient, 'sendRequest').mockImplementation(() => Promise.resolve(getWorkflowsUrlResponse)); + jest.spyOn(utility, 'getDeploymentConfig').mockImplementation(() => Promise.resolve(deploymentConfig)); const kubeCtl: jest.Mocked = new Kubectl("") as any; kubeCtl.apply = jest.fn().mockReturnValue(""); diff --git a/lib/constants.js b/lib/constants.js index 9ee05150..71a79ac0 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -25,21 +25,24 @@ ServiceTypes.clusterIP = 'ClusterIP'; exports.deploymentTypes = ['deployment', 'replicaset', 'daemonset', 'pod', 'statefulset']; exports.workloadTypes = ['deployment', 'replicaset', 'daemonset', 'pod', 'statefulset', 'job', 'cronjob']; exports.workloadTypesWithRolloutStatus = ['deployment', 'daemonset', 'statefulset']; -function getWorkflowAnnotationsJson(lastSuccessRunSha, workflowFilePath) { - return `{` - + `'run': '${process.env.GITHUB_RUN_ID}',` - + `'repository': '${process.env.GITHUB_REPOSITORY}',` - + `'workflow': '${process.env.GITHUB_WORKFLOW}',` - + `'workflowFileName': '${workflowFilePath.replace(".github/workflows/", "")}',` - + `'jobName': '${process.env.GITHUB_JOB}',` - + `'createdBy': '${process.env.GITHUB_ACTOR}',` - + `'runUri': 'https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}',` - + `'commit': '${process.env.GITHUB_SHA}',` - + `'lastSuccessRunCommit': '${lastSuccessRunSha}',` - + `'branch': '${process.env.GITHUB_REF}',` - + `'deployTimestamp': '${Date.now()}',` - + `'provider': 'GitHub'` - + `}`; +function getWorkflowAnnotationsJson(lastSuccessRunSha, workflowFilePath, deploymentConfig) { + let annotationObject = {}; + annotationObject["run"] = process.env.GITHUB_RUN_ID; + annotationObject["repository"] = process.env.GITHUB_REPOSITORY; + annotationObject["workflow"] = process.env.GITHUB_WORKFLOW; + annotationObject["workflowFileName"] = workflowFilePath.replace(".github/workflows/", ""); + annotationObject["jobName"] = process.env.GITHUB_JOB; + annotationObject["createdBy"] = process.env.GITHUB_ACTOR; + annotationObject["runUri"] = `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; + annotationObject["commit"] = process.env.GITHUB_SHA; + annotationObject["lastSuccessRunCommit"] = lastSuccessRunSha; + annotationObject["branch"] = process.env.GITHUB_REF; + annotationObject["deployTimestamp"] = Date.now(); + annotationObject["dockerfilePaths"] = deploymentConfig.dockerfilePaths; + annotationObject["manifestsPaths"] = deploymentConfig.manifestFilePaths; + annotationObject["helmChartPaths"] = deploymentConfig.helmChartFilePaths; + annotationObject["provider"] = "GitHub"; + return JSON.stringify(annotationObject); } exports.getWorkflowAnnotationsJson = getWorkflowAnnotationsJson; function getWorkflowAnnotationKeyLabel(workflowFilePath) { diff --git a/lib/docker-object-model.js b/lib/docker-object-model.js new file mode 100644 index 00000000..8b975f4c --- /dev/null +++ b/lib/docker-object-model.js @@ -0,0 +1,31 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DockerExec = void 0; +const tool_runner_1 = require("./utilities/tool-runner"); +class DockerExec { + constructor(dockerPath) { + this.dockerPath = dockerPath; + } + ; + pull(image, args, silent) { + args = ['pull', image, ...args]; + let result = this.execute(args, silent); + if (result.stderr != '' && result.code != 0) { + throw new Error(`docker images pull failed with: ${result.error}`); + } + } + inspect(image, args, silent) { + args = ['inspect', image, ...args]; + let result = this.execute(args, silent); + if (result.stderr != '' && result.code != 0) { + throw new Error(`docker inspect call failed with: ${result.error}`); + } + return result.stdout; + } + execute(args, silent) { + const command = new tool_runner_1.ToolRunner(this.dockerPath); + command.arg(args); + return command.execSync({ silent: !!silent }); + } +} +exports.DockerExec = DockerExec; diff --git a/lib/utilities/strategy-helpers/deployment-helper.js b/lib/utilities/strategy-helpers/deployment-helper.js index 88f35470..525a2f0a 100644 --- a/lib/utilities/strategy-helpers/deployment-helper.js +++ b/lib/utilities/strategy-helpers/deployment-helper.js @@ -134,15 +134,16 @@ function checkManifestStability(kubectl, resources) { function annotateAndLabelResources(files, kubectl, resourceTypes, allPods) { return __awaiter(this, void 0, void 0, function* () { const workflowFilePath = yield utility_1.getWorkflowFilePath(TaskInputParameters.githubToken); + const deploymentConfig = yield utility_1.getDeploymentConfig(); const annotationKeyLabel = models.getWorkflowAnnotationKeyLabel(workflowFilePath); - annotateResources(files, kubectl, resourceTypes, allPods, annotationKeyLabel, workflowFilePath); + annotateResources(files, kubectl, resourceTypes, allPods, annotationKeyLabel, workflowFilePath, deploymentConfig); labelResources(files, kubectl, annotationKeyLabel); }); } -function annotateResources(files, kubectl, resourceTypes, allPods, annotationKey, workflowFilePath) { +function annotateResources(files, kubectl, resourceTypes, allPods, annotationKey, workflowFilePath, deploymentConfig) { const annotateResults = []; const lastSuccessSha = utility_1.getLastSuccessfulRunSha(kubectl, TaskInputParameters.namespace, annotationKey); - let annotationKeyValStr = annotationKey + '=' + models.getWorkflowAnnotationsJson(lastSuccessSha, workflowFilePath); + let annotationKeyValStr = annotationKey + '=' + models.getWorkflowAnnotationsJson(lastSuccessSha, workflowFilePath, deploymentConfig); annotateResults.push(kubectl.annotate('namespace', TaskInputParameters.namespace, annotationKeyValStr)); annotateResults.push(kubectl.annotateFiles(files, annotationKeyValStr)); resourceTypes.forEach(resource => { diff --git a/lib/utilities/utility.js b/lib/utilities/utility.js index 5a360d3b..00fdab8b 100644 --- a/lib/utilities/utility.js +++ b/lib/utilities/utility.js @@ -9,11 +9,14 @@ 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.getWorkflowFilePath = exports.getLastSuccessfulRunSha = exports.checkForErrors = exports.isEqual = exports.getExecutableExtension = void 0; +exports.getCurrentTime = exports.getRandomInt = exports.sleep = exports.getDeploymentConfig = 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"); const httpClient_1 = require("./httpClient"); +const inputParams = require("../input-parameters"); +const docker_object_model_1 = require("../docker-object-model"); +const io = require("@actions/io"); function getExecutableExtension() { if (os.type().match(/^Win/)) { return '.exe'; @@ -138,6 +141,36 @@ function annotateChildPods(kubectl, resourceType, resourceName, annotationKeyVal return commandExecutionResults; } exports.annotateChildPods = annotateChildPods; +function getDeploymentConfig() { + return __awaiter(this, void 0, void 0, function* () { + const inputManifestFiles = inputParams.manifests || []; + const helmChartPaths = (process.env.HELM_CHART_PATHS && process.env.HELM_CHART_PATHS.split('\n').filter(path => path != "")) || []; + const imageNames = inputParams.containers || []; + let imageDockerfilePathMap = {}; + //Fetching from image label if available + for (const image of imageNames) { + let imageConfig, imageInspectResult; + try { + yield checkDockerPath(); + var dockerExec = new docker_object_model_1.DockerExec('docker'); + dockerExec.pull(image, [], true); + imageInspectResult = dockerExec.inspect(image, [], true); + imageConfig = JSON.parse(imageInspectResult)[0]; + imageDockerfilePathMap[image] = getDockerfilePath(imageConfig); + } + catch (ex) { + core.warning(`Failed to get dockerfile path for image ${image.toString()} | ` + ex); + } + } + const deploymentConfig = { + manifestFilePaths: inputManifestFiles, + helmChartFilePaths: helmChartPaths, + dockerfilePaths: imageDockerfilePathMap + }; + return Promise.resolve(deploymentConfig); + }); +} +exports.getDeploymentConfig = getDeploymentConfig; function sleep(timeout) { return new Promise(resolve => setTimeout(resolve, timeout)); } @@ -150,3 +183,32 @@ function getCurrentTime() { return new Date().getTime(); } exports.getCurrentTime = getCurrentTime; +function checkDockerPath() { + return __awaiter(this, void 0, void 0, function* () { + let dockerPath = yield io.which('docker', false); + if (!dockerPath) { + throw new Error('Docker is not installed.'); + } + }); +} +function getDockerfilePath(imageConfig) { + const DOCKERFILE_PATH_LABEL_KEY = 'dockerfile-path'; + const ref = process.env.GITHUB_REF && process.env.GITHUB_REF.replace('refs/heads/', '').replace('refs/tags/', ''); + let pathLabel, pathLink, pathValue = ''; + if (imageConfig) { + if ((imageConfig.Config) && (imageConfig.Config.Labels) && (imageConfig.Config.Labels[DOCKERFILE_PATH_LABEL_KEY])) { + pathLabel = imageConfig.Config.Labels[DOCKERFILE_PATH_LABEL_KEY]; + if (pathValue.startsWith('./')) { //if it is relative filepath convert to link from current repo + pathLink = `https://github.com/${process.env.GITHUB_REPOSITORY}/blob/${ref}/${pathLabel}`; + pathValue = pathLink; + } + else { + pathValue = pathLabel; + } + } + else { + pathValue = ''; + } + } + return pathValue; +} diff --git a/src/constants.ts b/src/constants.ts index 62d1bdf0..14f7b3fd 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,4 +1,5 @@ 'use strict'; +import { DeploymentConfig } from "./utilities/utility"; export class KubernetesWorkload { public static pod: string = 'Pod'; @@ -25,21 +26,25 @@ export const deploymentTypes: string[] = ['deployment', 'replicaset', 'daemonset export const workloadTypes: string[] = ['deployment', 'replicaset', 'daemonset', 'pod', 'statefulset', 'job', 'cronjob']; export const workloadTypesWithRolloutStatus: string[] = ['deployment', 'daemonset', 'statefulset']; -export function getWorkflowAnnotationsJson(lastSuccessRunSha: string, workflowFilePath: string): string { - return `{` - + `'run': '${process.env.GITHUB_RUN_ID}',` - + `'repository': '${process.env.GITHUB_REPOSITORY}',` - + `'workflow': '${process.env.GITHUB_WORKFLOW}',` - + `'workflowFileName': '${workflowFilePath.replace(".github/workflows/", "")}',` - + `'jobName': '${process.env.GITHUB_JOB}',` - + `'createdBy': '${process.env.GITHUB_ACTOR}',` - + `'runUri': 'https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}',` - + `'commit': '${process.env.GITHUB_SHA}',` - + `'lastSuccessRunCommit': '${lastSuccessRunSha}',` - + `'branch': '${process.env.GITHUB_REF}',` - + `'deployTimestamp': '${Date.now()}',` - + `'provider': 'GitHub'` - + `}`; +export function getWorkflowAnnotationsJson(lastSuccessRunSha: string, workflowFilePath: string, deploymentConfig: DeploymentConfig): string { + let annotationObject: any = {}; + annotationObject["run"] = process.env.GITHUB_RUN_ID; + annotationObject["repository"] = process.env.GITHUB_REPOSITORY; + annotationObject["workflow"] = process.env.GITHUB_WORKFLOW; + annotationObject["workflowFileName"] = workflowFilePath.replace(".github/workflows/", ""); + annotationObject["jobName"] = process.env.GITHUB_JOB; + annotationObject["createdBy"] = process.env.GITHUB_ACTOR; + annotationObject["runUri"] = `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; + annotationObject["commit"] = process.env.GITHUB_SHA; + annotationObject["lastSuccessRunCommit"] = lastSuccessRunSha; + annotationObject["branch"] = process.env.GITHUB_REF; + annotationObject["deployTimestamp"] = Date.now(); + annotationObject["dockerfilePaths"] = deploymentConfig.dockerfilePaths; + annotationObject["manifestsPaths"] = deploymentConfig.manifestFilePaths + annotationObject["helmChartPaths"] = deploymentConfig.helmChartFilePaths; + annotationObject["provider"] = "GitHub"; + + return JSON.stringify(annotationObject); } export function getWorkflowAnnotationKeyLabel(workflowFilePath: string): string { diff --git a/src/docker-object-model.ts b/src/docker-object-model.ts new file mode 100644 index 00000000..d18ca20d --- /dev/null +++ b/src/docker-object-model.ts @@ -0,0 +1,33 @@ +import { ToolRunner, IExecOptions, IExecSyncResult } from "./utilities/tool-runner"; + +export class DockerExec { + private dockerPath: string; + + constructor(dockerPath: string) { + this.dockerPath = dockerPath; + }; + + public pull(image: string, args: string[], silent?: boolean) { + args = ['pull', image, ...args]; + let result: IExecSyncResult = this.execute(args, silent); + if (result.stderr != '' && result.code != 0) { + throw new Error(`docker images pull failed with: ${result.error}`); + } + } + + public inspect(image: string, args: string[], silent?: boolean): any { + args = ['inspect', image, ...args]; + let result: IExecSyncResult = this.execute(args, silent); + if (result.stderr != '' && result.code != 0) { + throw new Error(`docker inspect call failed with: ${result.error}`); + } + return result.stdout; + } + + private execute(args: string[], silent?: boolean) { + const command = new ToolRunner(this.dockerPath); + command.arg(args); + + return command.execSync({ silent: !!silent } as IExecOptions); + } +} diff --git a/src/utilities/strategy-helpers/deployment-helper.ts b/src/utilities/strategy-helpers/deployment-helper.ts index d8d2fb72..58bcaa51 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, getWorkflowFilePath, getLastSuccessfulRunSha } from "../utility"; +import { checkForErrors, annotateChildPods, getWorkflowFilePath, getLastSuccessfulRunSha, DeploymentConfig, getDeploymentConfig } from "../utility"; import { isBlueGreenDeploymentStrategy, isIngressRoute, isSMIRoute, routeBlueGreen } from './blue-green-helper'; import { deployBlueGreenService } from './service-blue-green-helper'; import { deployBlueGreenIngress } from './ingress-blue-green-helper'; @@ -45,7 +45,7 @@ export async function deploy(kubectl: Kubectl, manifestFilePaths: string[], depl ingressResources.forEach(ingressResource => { kubectl.getResource(KubernetesConstants.DiscoveryAndLoadBalancerResource.ingress, ingressResource.name); }); - + // annotate resources let allPods: any; try { @@ -79,7 +79,7 @@ function deployManifests(files: string[], kubectl: Kubectl, isCanaryDeploymentSt result = canaryDeploymentOutput.result; files = canaryDeploymentOutput.newFilePaths; } else if (isBlueGreenDeploymentStrategy) { - let blueGreenDeploymentOutput: any; + let blueGreenDeploymentOutput: any; if (isIngressRoute()) { blueGreenDeploymentOutput = deployBlueGreenIngress(kubectl, files); } else if (isSMIRoute()) { @@ -130,15 +130,16 @@ async function checkManifestStability(kubectl: Kubectl, resources: Resource[]): async function annotateAndLabelResources(files: string[], kubectl: Kubectl, resourceTypes: Resource[], allPods: any) { const workflowFilePath = await getWorkflowFilePath(TaskInputParameters.githubToken); + const deploymentConfig = await getDeploymentConfig(); const annotationKeyLabel = models.getWorkflowAnnotationKeyLabel(workflowFilePath); - annotateResources(files, kubectl, resourceTypes, allPods, annotationKeyLabel, workflowFilePath); + annotateResources(files, kubectl, resourceTypes, allPods, annotationKeyLabel, workflowFilePath, deploymentConfig); labelResources(files, kubectl, annotationKeyLabel); } -function annotateResources(files: string[], kubectl: Kubectl, resourceTypes: Resource[], allPods: any, annotationKey: string, workflowFilePath: string) { +function annotateResources(files: string[], kubectl: Kubectl, resourceTypes: Resource[], allPods: any, annotationKey: string, workflowFilePath: string, deploymentConfig: DeploymentConfig) { const annotateResults: IExecSyncResult[] = []; const lastSuccessSha = getLastSuccessfulRunSha(kubectl, TaskInputParameters.namespace, annotationKey); - let annotationKeyValStr = annotationKey + '=' + models.getWorkflowAnnotationsJson(lastSuccessSha, workflowFilePath); + let annotationKeyValStr = annotationKey + '=' + models.getWorkflowAnnotationsJson(lastSuccessSha, workflowFilePath, deploymentConfig); annotateResults.push(kubectl.annotate('namespace', TaskInputParameters.namespace, annotationKeyValStr)); annotateResults.push(kubectl.annotateFiles(files, annotationKeyValStr)); resourceTypes.forEach(resource => { diff --git a/src/utilities/utility.ts b/src/utilities/utility.ts index 49f96da2..a5d2ba41 100644 --- a/src/utilities/utility.ts +++ b/src/utilities/utility.ts @@ -4,6 +4,15 @@ import { IExecSyncResult } from './tool-runner'; import { Kubectl } from '../kubectl-object-model'; import { GitHubClient } from '../githubClient'; import { StatusCodes } from "./httpClient"; +import * as inputParams from "../input-parameters"; +import { DockerExec } from '../docker-object-model'; +import * as io from '@actions/io'; + +export interface DeploymentConfig { + manifestFilePaths: string[]; + helmChartFilePaths: string[]; + dockerfilePaths: any; +} export function getExecutableExtension(): string { if (os.type().match(/^Win/)) { @@ -129,6 +138,39 @@ export function annotateChildPods(kubectl: Kubectl, resourceType: string, resour return commandExecutionResults; } +export async function getDeploymentConfig(): Promise { + + const inputManifestFiles = inputParams.manifests || []; + const helmChartPaths = (process.env.HELM_CHART_PATHS && process.env.HELM_CHART_PATHS.split('\n').filter(path => path != "")) || []; + const imageNames = inputParams.containers || []; + let imageDockerfilePathMap: { [id: string]: string; } = {}; + + //Fetching from image label if available + for (const image of imageNames) { + let imageConfig: any, imageInspectResult: string; + + try { + await checkDockerPath(); + var dockerExec: DockerExec = new DockerExec('docker'); + dockerExec.pull(image, [], true); + imageInspectResult = dockerExec.inspect(image, [], true); + imageConfig = JSON.parse(imageInspectResult)[0]; + imageDockerfilePathMap[image] = getDockerfilePath(imageConfig); + } + catch (ex) { + core.warning(`Failed to get dockerfile path for image ${image.toString()} | ` + ex); + } + } + + const deploymentConfig = { + manifestFilePaths: inputManifestFiles, + helmChartFilePaths: helmChartPaths, + dockerfilePaths: imageDockerfilePathMap + }; + + return Promise.resolve(deploymentConfig); +} + export function sleep(timeout: number) { return new Promise(resolve => setTimeout(resolve, timeout)); } @@ -140,3 +182,32 @@ export function getRandomInt(max: number) { export function getCurrentTime(): number { return new Date().getTime(); } + +async function checkDockerPath() { + let dockerPath = await io.which('docker', false); + if (!dockerPath) { + throw new Error('Docker is not installed.'); + } +} + +function getDockerfilePath(imageConfig: any): string { + const DOCKERFILE_PATH_LABEL_KEY = 'dockerfile-path'; + const ref: string = process.env.GITHUB_REF && process.env.GITHUB_REF.replace('refs/heads/', '').replace('refs/tags/', ''); + let pathLabel: string, pathLink: string, pathValue: string = ''; + if (imageConfig) { + if ((imageConfig.Config) && (imageConfig.Config.Labels) && (imageConfig.Config.Labels[DOCKERFILE_PATH_LABEL_KEY])) { + pathLabel = imageConfig.Config.Labels[DOCKERFILE_PATH_LABEL_KEY]; + if (pathValue.startsWith('./')) { //if it is relative filepath convert to link from current repo + pathLink = `https://github.com/${process.env.GITHUB_REPOSITORY}/blob/${ref}/${pathLabel}`; + pathValue = pathLink; + } + else { + pathValue = pathLabel; + } + } + else { + pathValue = ''; + } + } + return pathValue; +} \ No newline at end of file