From d7d5d3429f2f4fc98e6f10fcc77a5d26938c4150 Mon Sep 17 00:00:00 2001 From: Jyotsna Date: Tue, 29 Dec 2020 19:11:56 +0530 Subject: [PATCH] New traceability fields annotation changes - design changes (#89) * New design changes --- __tests__/run.test.ts | 10 +- lib/constants.js | 8 +- lib/docker-object-model.js | 31 ++++ lib/utilities/exec.js | 35 ----- .../strategy-helpers/deployment-helper.js | 8 +- lib/utilities/utility.js | 119 +++++++-------- src/constants.ts | 9 +- src/docker-object-model.ts | 33 +++++ src/utilities/exec.ts | 33 ----- .../strategy-helpers/deployment-helper.ts | 10 +- src/utilities/utility.ts | 140 ++++++++---------- 11 files changed, 201 insertions(+), 235 deletions(-) create mode 100644 lib/docker-object-model.js delete mode 100644 lib/utilities/exec.js create mode 100644 src/docker-object-model.ts delete mode 100644 src/utilities/exec.ts diff --git a/__tests__/run.test.ts b/__tests__/run.test.ts index f393f1fd..cc65116d 100644 --- a/__tests__/run.test.ts +++ b/__tests__/run.test.ts @@ -293,8 +293,8 @@ test("deployment - deploy() - deploy force flag on", async () => { }); test("deployment - deploy() - Annotate & label resources", async () => { - let filepathConfigs = { manifestsPaths :['manifests/deployment.yaml'], helmChartPaths : [], dockerfilePaths :{}} ; - let annotationKeyValStr = getWorkflowAnnotationKeyLabel(process.env.GITHUB_WORKFLOW) + '=' + getWorkflowAnnotationsJson('currentCommit', '.github/workflows/workflow.yml', filepathConfigs); + let 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); @@ -302,7 +302,7 @@ test("deployment - deploy() - Annotate & label resources", async () => { const fileHelperMock = mocked(fileHelper, true); fileHelperMock.writeObjectsToFile = jest.fn().mockReturnValue(["~/Deployment_testapp_currentTimestamp"]); jest.spyOn(utility, 'getWorkflowFilePath').mockImplementation(() => Promise.resolve(process.env.GITHUB_WORKFLOW)); - jest.spyOn(utility, 'getFilePathsConfigs').mockImplementation(()=> Promise.resolve(filepathConfigs)); + jest.spyOn(utility, 'getDeploymentConfig').mockImplementation(()=> Promise.resolve(deploymentConfig)); const kubeCtl: jest.Mocked = new Kubectl("") as any; kubeCtl.apply = jest.fn().mockReturnValue(""); @@ -323,8 +323,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 filepathConfigs = { manifestsPaths :['manifests/deployment.yaml'], helmChartPaths : [], dockerfilePaths :{}} ; - let annotationKeyValStr = getWorkflowAnnotationKeyLabel(process.env.GITHUB_WORKFLOW) + '=' + getWorkflowAnnotationsJson('NA', '.github/workflows/NewWorkflow.yml', filepathConfigs); + let 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); diff --git a/lib/constants.js b/lib/constants.js index 1292d59b..71a79ac0 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -25,7 +25,7 @@ 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, filePathConfigs) { +function getWorkflowAnnotationsJson(lastSuccessRunSha, workflowFilePath, deploymentConfig) { let annotationObject = {}; annotationObject["run"] = process.env.GITHUB_RUN_ID; annotationObject["repository"] = process.env.GITHUB_REPOSITORY; @@ -38,9 +38,9 @@ function getWorkflowAnnotationsJson(lastSuccessRunSha, workflowFilePath, filePat annotationObject["lastSuccessRunCommit"] = lastSuccessRunSha; annotationObject["branch"] = process.env.GITHUB_REF; annotationObject["deployTimestamp"] = Date.now(); - annotationObject["dockerfilePaths"] = filePathConfigs.dockerfilePaths; - annotationObject["manifestsPaths"] = filePathConfigs.manifestFilePaths; - annotationObject["helmChartPaths"] = filePathConfigs.helmChartFilePaths; + annotationObject["dockerfilePaths"] = deploymentConfig.dockerfilePaths; + annotationObject["manifestsPaths"] = deploymentConfig.manifestFilePaths; + annotationObject["helmChartPaths"] = deploymentConfig.helmChartFilePaths; annotationObject["provider"] = "GitHub"; return JSON.stringify(annotationObject); } diff --git a/lib/docker-object-model.js b/lib/docker-object-model.js new file mode 100644 index 00000000..48a1368b --- /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/exec.js b/lib/utilities/exec.js deleted file mode 100644 index 89b86fcd..00000000 --- a/lib/utilities/exec.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.exec = void 0; -const aexec = require("@actions/exec"); -exports.exec = (command, args = [], silent) => __awaiter(void 0, void 0, void 0, function* () { - let stdout = ''; - let stderr = ''; - const options = { - silent: silent, - ignoreReturnCode: true - }; - options.listeners = { - stdout: (data) => { - stdout += data.toString(); - }, - stderr: (data) => { - stderr += data.toString(); - } - }; - const returnCode = yield aexec.exec(command, args, options); - return { - success: returnCode === 0, - stdout: stdout.trim(), - stderr: stderr.trim() - }; -}); diff --git a/lib/utilities/strategy-helpers/deployment-helper.js b/lib/utilities/strategy-helpers/deployment-helper.js index c11d3a17..2b93a504 100644 --- a/lib/utilities/strategy-helpers/deployment-helper.js +++ b/lib/utilities/strategy-helpers/deployment-helper.js @@ -113,16 +113,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 filePathsConfig = yield utility_1.getFilePathsConfigs(); + const deploymentConfig = yield utility_1.getDeploymentConfig(); const annotationKeyLabel = models.getWorkflowAnnotationKeyLabel(workflowFilePath); - annotateResources(files, kubectl, resourceTypes, allPods, annotationKeyLabel, workflowFilePath, filePathsConfig); + annotateResources(files, kubectl, resourceTypes, allPods, annotationKeyLabel, workflowFilePath, deploymentConfig); labelResources(files, kubectl, annotationKeyLabel); }); } -function annotateResources(files, kubectl, resourceTypes, allPods, annotationKey, workflowFilePath, filePathsConfig) { +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, filePathsConfig); + 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 f363e39d..f82884e6 100644 --- a/lib/utilities/utility.js +++ b/lib/utilities/utility.js @@ -9,13 +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.getFilePathsConfigs = 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 exec = require("./exec"); 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'; @@ -139,81 +140,36 @@ function annotateChildPods(kubectl, resourceType, resourceName, annotationKeyVal return commandExecutionResults; } exports.annotateChildPods = annotateChildPods; -function getFilePathsConfigs() { +function getDeploymentConfig() { return __awaiter(this, void 0, void 0, function* () { - let filePathsConfig = {}; - const MANIFEST_PATHS_KEY = 'manifestFilePaths'; - const HELM_CHART_KEY = 'helmChartFilePaths'; - const DOCKERFILE_PATH_KEY = 'dockerfilePaths'; - const DOCKERFILE_PATH_LABEL_KEY = 'dockerfile-path'; - let inputManifestFiles = inputParams.manifests || []; - filePathsConfig[MANIFEST_PATHS_KEY] = inputManifestFiles; - let helmChartPaths = (process.env.HELM_CHART_PATHS && process.env.HELM_CHART_PATHS.split('\n').filter(path => path != "")) || []; - filePathsConfig[HELM_CHART_KEY] = helmChartPaths; - //Parsing dockerfile paths for images - let imageNames = core.getInput('images').split('\n'); + 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 = {}; - let pathKey, pathVal; - //Fetching from env var if available - let dockerfilePathsList = (process.env.DOCKERFILE_PATHS && process.env.DOCKERFILE_PATHS.split('\n')) || []; - dockerfilePathsList.forEach(path => { - if (path) { - pathKey = path.split(' ')[0]; - pathVal = path.split(' ')[1]; - imageDockerfilePathMap[pathKey] = pathVal; - } - }); - //Fetching from image lable if available + //Fetching from image label if available for (const image of imageNames) { - let args = [image]; - let resultObj; - let containerRegistryName = image; + let imageConfig, imageInspectResult; try { - let usrname = process.env.CR_USERNAME || null; - let pwd = process.env.CR_PASSWORD || null; - if (pwd && usrname) { - let loginArgs = [containerRegistryName, '--username', usrname, '--password', pwd]; - yield exec.exec('docker login ', loginArgs, true).then(res => { - if (res.stderr != '' && !res.success) { - throw new Error(`docker login failed with: ${res.stderr.match(/(.*)\s*$/)[0]}`); - } - }); - } - yield exec.exec('docker pull ', args, true).then(res => { - if (res.stderr != '' && !res.success) { - throw new Error(`docker images pull failed with: ${res.stderr.match(/(.*)\s*$/)[0]}`); - } - }); - yield exec.exec('docker inspect --type=image', args, true).then(res => { - if (res.stderr != '' && !res.success) { - throw new Error(`docker inspect call failed with: ${res.stderr.match(/(.*)\s*$/)[0]}`); - } - if (res.stdout) { - resultObj = JSON.parse(res.stdout); - } - }); + 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 paths for image ${image.toString()} | ` + ex); - } - if (resultObj) { - resultObj = resultObj[0]; - if ((resultObj.Config) && (resultObj.Config.Labels) && (resultObj.Config.Labels[DOCKERFILE_PATH_LABEL_KEY])) { - pathVal = resultObj.Config.Labels[DOCKERFILE_PATH_LABEL_KEY]; - } - else { - pathVal = 'Not available'; - } - if (!imageDockerfilePathMap[image]) { //If (image : someVal) does not exist from env var parsing then add - imageDockerfilePathMap[image] = pathVal; - } + core.warning(`Failed to get dockerfile path for image ${image.toString()} | ` + ex); } } - filePathsConfig[DOCKERFILE_PATH_KEY] = imageDockerfilePathMap; - return Promise.resolve(filePathsConfig); + const deploymentConfig = { + manifestFilePaths: inputManifestFiles, + helmChartFilePaths: helmChartPaths, + dockerfilePaths: imageDockerfilePathMap + }; + return Promise.resolve(deploymentConfig); }); } -exports.getFilePathsConfigs = getFilePathsConfigs; +exports.getDeploymentConfig = getDeploymentConfig; function sleep(timeout) { return new Promise(resolve => setTimeout(resolve, timeout)); } @@ -226,3 +182,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 359c098a..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,7 +26,7 @@ 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, filePathConfigs: any): string { +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; @@ -38,9 +39,9 @@ export function getWorkflowAnnotationsJson(lastSuccessRunSha: string, workflowFi annotationObject["lastSuccessRunCommit"] = lastSuccessRunSha; annotationObject["branch"] = process.env.GITHUB_REF; annotationObject["deployTimestamp"] = Date.now(); - annotationObject["dockerfilePaths"] = filePathConfigs.dockerfilePaths; - annotationObject["manifestsPaths"] = filePathConfigs.manifestFilePaths - annotationObject["helmChartPaths"] = filePathConfigs.helmChartFilePaths; + annotationObject["dockerfilePaths"] = deploymentConfig.dockerfilePaths; + annotationObject["manifestsPaths"] = deploymentConfig.manifestFilePaths + annotationObject["helmChartPaths"] = deploymentConfig.helmChartFilePaths; annotationObject["provider"] = "GitHub"; return JSON.stringify(annotationObject); diff --git a/src/docker-object-model.ts b/src/docker-object-model.ts new file mode 100644 index 00000000..66ed7c7f --- /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); + } +} \ No newline at end of file diff --git a/src/utilities/exec.ts b/src/utilities/exec.ts deleted file mode 100644 index ac2c00ec..00000000 --- a/src/utilities/exec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import * as aexec from '@actions/exec'; - -export interface ExecResult { - success: boolean; - stdout: string; - stderr: string; -} - -export const exec = async (command: string, args: string[] = [], silent?: boolean): Promise => { - let stdout: string = ''; - let stderr: string = ''; - - const options: aexec.ExecOptions = { - silent: silent, - ignoreReturnCode: true - }; - options.listeners = { - stdout: (data: Buffer) => { - stdout += data.toString(); - }, - stderr: (data: Buffer) => { - stderr += data.toString(); - } - }; - - const returnCode: number = await aexec.exec(command, args, options); - - return { - success: returnCode === 0, - stdout: stdout.trim(), - stderr: stderr.trim() - }; -}; \ 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 78308cbc..c877837b 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, getFilePathsConfigs } from "../utility"; +import { checkForErrors, annotateChildPods, getWorkflowFilePath, getLastSuccessfulRunSha, getDeploymentConfig, DeploymentConfig } from "../utility"; export async function deploy(kubectl: Kubectl, manifestFilePaths: string[], deploymentStrategy: string) { @@ -114,16 +114,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 filePathsConfig = await getFilePathsConfigs(); + const deploymentConfig = await getDeploymentConfig(); const annotationKeyLabel = models.getWorkflowAnnotationKeyLabel(workflowFilePath); - annotateResources(files, kubectl, resourceTypes, allPods, annotationKeyLabel, workflowFilePath, filePathsConfig); + 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, filePathsConfig: 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, filePathsConfig); + 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 17aef732..ded612d7 100644 --- a/src/utilities/utility.ts +++ b/src/utilities/utility.ts @@ -4,8 +4,15 @@ import { IExecSyncResult } from './tool-runner'; import { Kubectl } from '../kubectl-object-model'; import { GitHubClient } from '../githubClient'; import { StatusCodes } from "./httpClient"; -import * as exec from "./exec"; 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,93 +136,40 @@ export function annotateChildPods(kubectl: Kubectl, resourceType: string, resour return commandExecutionResults; } -export async function getFilePathsConfigs(): Promise { +export async function getDeploymentConfig(): Promise { - let filePathsConfig: any = {}; - const MANIFEST_PATHS_KEY = 'manifestFilePaths'; - const HELM_CHART_KEY = 'helmChartFilePaths'; - const DOCKERFILE_PATH_KEY = 'dockerfilePaths'; - const DOCKERFILE_PATH_LABEL_KEY = 'dockerfile-path'; + 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; } = {}; - let inputManifestFiles = inputParams.manifests || []; - filePathsConfig[MANIFEST_PATHS_KEY] = inputManifestFiles; + //Fetching from image label if available + for (const image of imageNames) { + let imageConfig: any, imageInspectResult: string; - let helmChartPaths = (process.env.HELM_CHART_PATHS && process.env.HELM_CHART_PATHS.split('\n').filter(path => path != "")) || []; - filePathsConfig[HELM_CHART_KEY] = helmChartPaths; - - //Parsing dockerfile paths for images - let imageNames = core.getInput('images').split('\n'); - let imageDockerfilePathMap: any = {}; - let pathKey: any, pathVal: any; - - //Fetching from env var if available - let dockerfilePathsList: any[] = (process.env.DOCKERFILE_PATHS && process.env.DOCKERFILE_PATHS.split('\n')) || []; - dockerfilePathsList.forEach(path => { - if(path){ - pathKey = path.split(' ')[0]; - pathVal = path.split(' ')[1]; - imageDockerfilePathMap[pathKey] = pathVal; - } - }) - - //Fetching from image lable if available - for(const image of imageNames){ - let args: string[] = [image]; - let resultObj: any; - let containerRegistryName = image; - - try{ - let usrname = process.env.CR_USERNAME || null; - let pwd = process.env.CR_PASSWORD || null; - if(pwd && usrname) - { - let loginArgs: string[] = [containerRegistryName, '--username', usrname, '--password', pwd]; - await exec.exec('docker login ', loginArgs, true).then(res => { - if (res.stderr != '' && !res.success) { - throw new Error(`docker login failed with: ${res.stderr.match(/(.*)\s*$/)![0]}`); - } - }); - } - - await exec.exec('docker pull ', args, true).then(res => { - if (res.stderr != '' && !res.success) { - throw new Error(`docker images pull failed with: ${res.stderr.match(/(.*)\s*$/)![0]}`); - } - }); - - await exec.exec('docker inspect --type=image', args, true).then(res => { - if (res.stderr != '' && !res.success) { - throw new Error(`docker inspect call failed with: ${res.stderr.match(/(.*)\s*$/)![0]}`); - } - - if(res.stdout){ - resultObj = JSON.parse(res.stdout); - } - }); + 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 paths for image ${image.toString()} | ` + ex); - } - - if(resultObj){ - resultObj = resultObj[0]; - if((resultObj.Config) && (resultObj.Config.Labels) && (resultObj.Config.Labels[DOCKERFILE_PATH_LABEL_KEY])){ - pathVal = resultObj.Config.Labels[DOCKERFILE_PATH_LABEL_KEY]; - } - else{ - pathVal = 'Not available'; - } - if(!imageDockerfilePathMap[image]){ //If (image : someVal) does not exist from env var parsing then add - imageDockerfilePathMap[image] = pathVal; - } + core.warning(`Failed to get dockerfile path for image ${image.toString()} | ` + ex); } } - - filePathsConfig[DOCKERFILE_PATH_KEY] = imageDockerfilePathMap; - return Promise.resolve(filePathsConfig); + const deploymentConfig = { + manifestFilePaths: inputManifestFiles, + helmChartFilePaths: helmChartPaths, + dockerfilePaths: imageDockerfilePathMap + }; + + return Promise.resolve(deploymentConfig); } + export function sleep(timeout: number) { return new Promise(resolve => setTimeout(resolve, timeout)); } @@ -227,3 +181,33 @@ 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; +} +