зеркало из https://github.com/Azure/k8s-deploy.git
Introduce annotations relevant to resource view during deploy
This commit is contained in:
Родитель
b4bc3003e8
Коммит
15e04b8f7e
|
@ -6,6 +6,8 @@ import * as deployment from '../src/utilities/strategy-helpers/deployment-helper
|
|||
import * as fs from 'fs';
|
||||
import * as io from '@actions/io';
|
||||
import * as toolCache from '@actions/tool-cache';
|
||||
import * as fileHelper from '../src/utilities/files-helper';
|
||||
import { workflowAnnotations } from '../src/constants';
|
||||
import * as utility from '../src/utilities/utility';
|
||||
import * as inputParam from '../src/input-parameters';
|
||||
|
||||
|
@ -15,6 +17,7 @@ import { getkubectlDownloadURL } from "../src/utilities/kubectl-util";
|
|||
import { mocked } from 'ts-jest/utils';
|
||||
|
||||
var path = require('path');
|
||||
const os = require("os");
|
||||
|
||||
const coreMock = mocked(core, true);
|
||||
const ioMock = mocked(io, true);
|
||||
|
@ -28,9 +31,29 @@ const stableVersionUrl = 'https://storage.googleapis.com/kubernetes-release/rele
|
|||
|
||||
var deploymentYaml = "";
|
||||
|
||||
const getAllPodsMock = {
|
||||
'code': 0,
|
||||
'stdout': '{"apiVersion": "v1","items": [{"apiVersion": "v1","kind": "Pod","metadata": {"labels": {"app": "testapp","pod-template-hash": "776cbc86f9"},"name": "testpod-776cbc86f9-pjrb6","namespace": "testnamespace","ownerReferences": [{"apiVersion": "apps/v1","blockOwnerDeletion": true,"controller": true,"kind": "ReplicaSet","name": "testpod-776cbc86f9","uid": "de544628-6589-4354-81fe-05faf00d336a"}],"resourceVersion": "12362496","selfLink": "/api/v1/namespaces/akskodey8187/pods/akskodey-776cbc86f9-pjrb6","uid": "c7d5f4c1-11a1-4884-8a66-09b015c72f69"},"spec": {"containers": [{"image": "imageId","imagePullPolicy": "IfNotPresent","name": "containerName","ports": [{"containerPort": 80,"protocol": "TCP"}]}]},"status": {"hostIP": "10.240.0.4","phase": "Running","podIP": "10.244.0.25","qosClass": "BestEffort","startTime": "2020-06-04T07:59:42Z"}}]}'
|
||||
};
|
||||
|
||||
const getNamespaceMock = {
|
||||
'code': 0,
|
||||
'stdout': '{"apiVersion": "v1","kind": "Namespace","metadata": {"annotations": {"workflow": ".github/workflows/workflow.yml","runUri": "https://github.com/testRepo/actions/runs/12345"}},"spec": {"finalizers": ["kubernetes"]},"status": {"phase": "Active"}}'
|
||||
};
|
||||
|
||||
const resources: Resource[] = [{ type: "Deployment", name: "AppName" }];
|
||||
|
||||
beforeAll(() => {
|
||||
deploymentYaml = fs.readFileSync(path.join(__dirname, 'manifests', 'deployment.yml'), 'utf8');
|
||||
|
||||
process.env["KUBECONFIG"] = 'kubeConfig';
|
||||
process.env['GITHUB_RUN_ID'] = '12345';
|
||||
process.env['GITHUB_WORKFLOW'] = '.github/workflows/workflow.yml';
|
||||
process.env['GITHUB_JOB'] = 'build-and-deploy';
|
||||
process.env['GITHUB_ACTOR'] = 'testUser';
|
||||
process.env['GITHUB_REPOSITORY'] = 'testRepo';
|
||||
process.env['GITHUB_SHA'] = 'testCommit';
|
||||
process.env['GITHUB_REF'] = 'testBranch';
|
||||
})
|
||||
|
||||
test("setKubectlPath() - install a particular version", async () => {
|
||||
|
@ -186,9 +209,12 @@ test("deployment - deploy() - Invokes with manifestfiles", async () => {
|
|||
const KubernetesObjectUtilityMock = mocked(KubernetesObjectUtility, true);
|
||||
const kubeCtl: jest.Mocked<Kubectl> = new Kubectl("") as any;
|
||||
kubeCtl.apply = jest.fn().mockReturnValue("");
|
||||
const resources: Resource[] = [{ type: "Deployment", name: "AppName" }];
|
||||
KubernetesObjectUtilityMock.getResources = jest.fn().mockReturnValue(resources);
|
||||
kubeCtl.getResource = jest.fn().mockReturnValue("");
|
||||
kubeCtl.getResource = jest.fn().mockReturnValue(getNamespaceMock);
|
||||
kubeCtl.getAllPods = jest.fn().mockReturnValue(getAllPodsMock);
|
||||
kubeCtl.describe = jest.fn().mockReturnValue("");
|
||||
kubeCtl.annotateFiles = jest.fn().mockReturnValue("");
|
||||
kubeCtl.annotate = jest.fn().mockReturnValue("");
|
||||
KubernetesManifestUtilityMock.checkManifestStability = jest.fn().mockReturnValue("");
|
||||
|
||||
const readFileSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => deploymentYaml);
|
||||
|
@ -199,36 +225,80 @@ test("deployment - deploy() - Invokes with manifestfiles", async () => {
|
|||
expect(kubeCtl.getResource).toBeCalledWith("ingress", "AppName");
|
||||
});
|
||||
|
||||
test("run() - deploy force flag on", async () => {
|
||||
const kubectlVersion = 'v1.18.0'
|
||||
test("deployment - deploy() - deploy force flag on", async () => {
|
||||
//Mocks
|
||||
coreMock.getInput = jest.fn().mockImplementation((name) => {
|
||||
if (name == 'manifests') {
|
||||
return 'manifests/deployment.yaml';
|
||||
}
|
||||
if (name == 'action') {
|
||||
return 'deploy';
|
||||
}
|
||||
if (name == 'strategy') {
|
||||
return undefined;
|
||||
}
|
||||
if (name == 'force') {
|
||||
return 'true';
|
||||
}
|
||||
return kubectlVersion;
|
||||
});
|
||||
|
||||
inputParamMock.forceDeployment = true;
|
||||
coreMock.setFailed = jest.fn();
|
||||
toolCacheMock.find = jest.fn().mockReturnValue('validPath');
|
||||
toolCacheMock.downloadTool = jest.fn().mockReturnValue('downloadpath');
|
||||
toolCacheMock.cacheFile = jest.fn().mockReturnValue('cachepath');
|
||||
fileUtility.chmodSync = jest.fn();
|
||||
utilityMock.checkForErrors = jest.fn();
|
||||
const deploySpy = jest.spyOn(Kubectl.prototype, 'apply').mockImplementation();
|
||||
const applyResMock = {
|
||||
'code': 0,
|
||||
'stderr': '',
|
||||
'error': Error(""),
|
||||
'stdout': 'changes configured'
|
||||
};
|
||||
const KubernetesManifestUtilityMock = mocked(KubernetesManifestUtility, true);
|
||||
const KubernetesObjectUtilityMock = mocked(KubernetesObjectUtility, true);
|
||||
const kubeCtl: jest.Mocked<Kubectl> = new Kubectl("") as any;
|
||||
KubernetesObjectUtilityMock.getResources = jest.fn().mockReturnValue(resources);
|
||||
kubeCtl.getResource = jest.fn().mockReturnValue(getNamespaceMock);
|
||||
kubeCtl.getAllPods = jest.fn().mockReturnValue(getAllPodsMock);
|
||||
kubeCtl.describe = jest.fn().mockReturnValue("");
|
||||
kubeCtl.annotateFiles = jest.fn().mockReturnValue("");
|
||||
kubeCtl.annotate = jest.fn().mockReturnValue("");
|
||||
KubernetesManifestUtilityMock.checkManifestStability = jest.fn().mockReturnValue("");
|
||||
|
||||
const deploySpy = jest.spyOn(kubeCtl, 'apply').mockImplementation(() => applyResMock);
|
||||
|
||||
//Invoke and assert
|
||||
await expect(action.run()).resolves.not.toThrow();
|
||||
await expect(deployment.deploy(kubeCtl, ['manifests/deployment.yaml'], undefined)).resolves.not.toThrowError();
|
||||
expect(deploySpy).toBeCalledWith(expect.anything(), true);
|
||||
deploySpy.mockRestore();
|
||||
});
|
||||
|
||||
test("deployment - deploy() - Annotate resources", async () => {
|
||||
const KubernetesManifestUtilityMock = mocked(KubernetesManifestUtility, true);
|
||||
KubernetesManifestUtilityMock.checkManifestStability = jest.fn().mockReturnValue("");
|
||||
const KubernetesObjectUtilityMock = mocked(KubernetesObjectUtility, true);
|
||||
KubernetesObjectUtilityMock.getResources = jest.fn().mockReturnValue(resources);
|
||||
const fileHelperMock = mocked(fileHelper, true);
|
||||
const fsMock = (mocked(fs, true));
|
||||
fileHelperMock.getTempDirectory = jest.fn().mockReturnValue("Local/Temp/");
|
||||
fsMock.writeFileSync =jest.fn().mockReturnValue("");
|
||||
const kubeCtl: jest.Mocked<Kubectl> = new Kubectl("") as any;
|
||||
kubeCtl.apply = jest.fn().mockReturnValue("");
|
||||
kubeCtl.getResource = jest.fn().mockReturnValue(getNamespaceMock);
|
||||
kubeCtl.getAllPods = jest.fn().mockReturnValue(getAllPodsMock);
|
||||
kubeCtl.getNewReplicaSet = jest.fn().mockReturnValue("testpod-776cbc86f9");
|
||||
kubeCtl.annotateFiles = jest.fn().mockReturnValue("");
|
||||
kubeCtl.annotate = jest.fn().mockReturnValue("");
|
||||
|
||||
//Invoke and assert
|
||||
await expect(deployment.deploy(kubeCtl, ['manifests/deployment.yaml'], undefined)).resolves.not.toThrowError();
|
||||
expect(kubeCtl.annotateFiles).toBeCalledWith(["Local\\Temp\\deployment.yaml"], workflowAnnotations, true);
|
||||
expect(kubeCtl.annotate).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
test("deployment - deploy() - Annotate resources failed", async () => {
|
||||
//Mocks
|
||||
inputParamMock.forceDeployment = true;
|
||||
const annotateMock = {
|
||||
'code': 1,
|
||||
'stderr': 'kubectl annotate failed',
|
||||
'error': Error(""),
|
||||
'stdout': ''
|
||||
};
|
||||
const KubernetesManifestUtilityMock = mocked(KubernetesManifestUtility, true);
|
||||
const KubernetesObjectUtilityMock = mocked(KubernetesObjectUtility, true);
|
||||
const kubeCtl: jest.Mocked<Kubectl> = new Kubectl("") as any;
|
||||
KubernetesObjectUtilityMock.getResources = jest.fn().mockReturnValue(resources);
|
||||
kubeCtl.apply = jest.fn().mockReturnValue("");
|
||||
kubeCtl.getResource = jest.fn().mockReturnValue(getNamespaceMock);
|
||||
kubeCtl.getAllPods = jest.fn().mockReturnValue(getAllPodsMock);
|
||||
kubeCtl.describe = jest.fn().mockReturnValue("");
|
||||
kubeCtl.annotateFiles = jest.fn().mockReturnValue("");
|
||||
kubeCtl.annotate = jest.fn().mockReturnValue(annotateMock);
|
||||
KubernetesManifestUtilityMock.checkManifestStability = jest.fn().mockReturnValue("");
|
||||
|
||||
const consoleOutputSpy = jest.spyOn(process.stdout, "write").mockImplementation();
|
||||
//Invoke and assert
|
||||
await expect(deployment.deploy(kubeCtl, ['manifests/deployment.yaml'], undefined)).resolves.not.toThrowError();
|
||||
expect(consoleOutputSpy).toHaveBeenNthCalledWith(1, '::warning::kubectl annotate failed' + os.EOL)
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.workloadTypesWithRolloutStatus = exports.workloadTypes = exports.deploymentTypes = exports.ServiceTypes = exports.DiscoveryAndLoadBalancerResource = exports.KubernetesWorkload = void 0;
|
||||
exports.workflowAnnotations = exports.workloadTypesWithRolloutStatus = exports.workloadTypes = exports.deploymentTypes = exports.ServiceTypes = exports.DiscoveryAndLoadBalancerResource = exports.KubernetesWorkload = void 0;
|
||||
class KubernetesWorkload {
|
||||
}
|
||||
exports.KubernetesWorkload = KubernetesWorkload;
|
||||
|
@ -25,3 +25,15 @@ ServiceTypes.clusterIP = 'ClusterIP';
|
|||
exports.deploymentTypes = ['deployment', 'replicaset', 'daemonset', 'pod', 'statefulset'];
|
||||
exports.workloadTypes = ['deployment', 'replicaset', 'daemonset', 'pod', 'statefulset', 'job', 'cronjob'];
|
||||
exports.workloadTypesWithRolloutStatus = ['deployment', 'daemonset', 'statefulset'];
|
||||
exports.workflowAnnotations = [
|
||||
`run=${process.env['GITHUB_RUN_ID']}`,
|
||||
`repository=${process.env['GITHUB_REPOSITORY']}`,
|
||||
`workflow=${process.env['GITHUB_WORKFLOW']}`,
|
||||
`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']}`,
|
||||
`branch=${process.env['GITHUB_REF']}`,
|
||||
`deployTimestamp=${Date.now()}`,
|
||||
`provider=GitHub`
|
||||
];
|
||||
|
|
|
@ -1,13 +1,4 @@
|
|||
"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.Kubectl = void 0;
|
||||
const tool_runner_1 = require("./utilities/tool-runner");
|
||||
|
@ -34,19 +25,34 @@ class Kubectl {
|
|||
return this.execute(['describe', resourceType, resourceName], silent);
|
||||
}
|
||||
getNewReplicaSet(deployment) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let newReplicaSet = '';
|
||||
const result = yield this.describe('deployment', deployment, true);
|
||||
if (result && result.stdout) {
|
||||
const stdout = result.stdout.split('\n');
|
||||
stdout.forEach((line) => {
|
||||
if (!!line && line.toLowerCase().indexOf('newreplicaset') > -1) {
|
||||
newReplicaSet = line.substr(14).trim().split(' ')[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
return newReplicaSet;
|
||||
});
|
||||
let newReplicaSet = '';
|
||||
const result = this.describe('deployment', deployment, true);
|
||||
if (result && result.stdout) {
|
||||
const stdout = result.stdout.split('\n');
|
||||
stdout.forEach((line) => {
|
||||
if (!!line && line.toLowerCase().indexOf('newreplicaset') > -1) {
|
||||
newReplicaSet = line.substr(14).trim().split(' ')[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
return newReplicaSet;
|
||||
}
|
||||
annotate(resourceType, resourceName, annotations, overwrite) {
|
||||
let args = ['annotate', resourceType, resourceName];
|
||||
args = args.concat(annotations);
|
||||
if (!!overwrite) {
|
||||
args.push(`--overwrite`);
|
||||
}
|
||||
return this.execute(args);
|
||||
}
|
||||
annotateFiles(files, annotations, overwrite) {
|
||||
let args = ['annotate'];
|
||||
args = args.concat(['-f', this.createInlineArray(files)]);
|
||||
args = args.concat(annotations);
|
||||
if (!!overwrite) {
|
||||
args.push(`--overwrite`);
|
||||
}
|
||||
return this.execute(args);
|
||||
}
|
||||
getAllPods() {
|
||||
return this.execute(['get', 'pods', '-o', 'json'], true);
|
||||
|
|
|
@ -252,7 +252,7 @@ function updateImagePullSecretsInManifestFiles(filePaths, imagePullSecrets) {
|
|||
}
|
||||
});
|
||||
});
|
||||
core.debug('New K8s objects after addin imagePullSecrets are :' + JSON.stringify(newObjectsList));
|
||||
core.debug('New K8s objects after adding imagePullSecrets are :' + JSON.stringify(newObjectsList));
|
||||
const newFilePaths = fileHelper.writeObjectsToFile(newObjectsList);
|
||||
return newFilePaths;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getResources = exports.updateSelectorLabels = exports.updateSpecLabels = exports.updateImagePullSecrets = exports.updateObjectLabels = exports.getReplicaCount = exports.isIngressEntity = exports.isServiceEntity = exports.isWorkloadEntity = exports.isDeploymentEntity = void 0;
|
||||
exports.getResources = exports.updateSelectorLabels = exports.updateSpecLabels = exports.updateImagePullSecrets = exports.updateObjectAnnotations = exports.updateObjectLabels = exports.getReplicaCount = exports.isIngressEntity = exports.isServiceEntity = exports.isWorkloadEntity = exports.isDeploymentEntity = void 0;
|
||||
const fs = require("fs");
|
||||
const core = require("@actions/core");
|
||||
const yaml = require("js-yaml");
|
||||
|
@ -78,6 +78,31 @@ function updateObjectLabels(inputObject, newLabels, override) {
|
|||
}
|
||||
}
|
||||
exports.updateObjectLabels = updateObjectLabels;
|
||||
function updateObjectAnnotations(inputObject, newAnnotations, override) {
|
||||
if (!inputObject) {
|
||||
throw ('NullInputObject');
|
||||
}
|
||||
if (!inputObject.metadata) {
|
||||
throw ('NullInputObjectMetadata');
|
||||
}
|
||||
if (!newAnnotations) {
|
||||
return;
|
||||
}
|
||||
if (override) {
|
||||
inputObject.metadata.annotations = newAnnotations;
|
||||
}
|
||||
else {
|
||||
let existingAnnotations = inputObject.metadata.annotations;
|
||||
if (!existingAnnotations) {
|
||||
existingAnnotations = new Map();
|
||||
}
|
||||
Object.keys(newAnnotations).forEach(function (key) {
|
||||
existingAnnotations[key] = newAnnotations[key];
|
||||
});
|
||||
inputObject.metadata.annotations = existingAnnotations;
|
||||
}
|
||||
}
|
||||
exports.updateObjectAnnotations = updateObjectAnnotations;
|
||||
function updateImagePullSecrets(inputObject, newImagePullSecrets, override) {
|
||||
if (!inputObject || !inputObject.spec || !newImagePullSecrets) {
|
||||
return;
|
||||
|
|
|
@ -155,6 +155,7 @@ function addCanaryLabelsAndAnnotations(inputObject, type) {
|
|||
const newLabels = new Map();
|
||||
newLabels[exports.CANARY_VERSION_LABEL] = type;
|
||||
helper.updateObjectLabels(inputObject, newLabels, false);
|
||||
helper.updateObjectAnnotations(inputObject, newLabels, false);
|
||||
helper.updateSelectorLabels(inputObject, newLabels, false);
|
||||
if (!helper.isServiceEntity(inputObject.kind)) {
|
||||
helper.updateSpecLabels(inputObject, newLabels, false);
|
||||
|
|
|
@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getManifestFiles = exports.deploy = void 0;
|
||||
const fs = require("fs");
|
||||
const core = require("@actions/core");
|
||||
const yaml = require("js-yaml");
|
||||
const canaryDeploymentHelper = require("./canary-deployment-helper");
|
||||
const KubernetesObjectUtility = require("../resource-object-utility");
|
||||
|
@ -46,6 +47,15 @@ function deploy(kubectl, manifestFilePaths, deploymentStrategy) {
|
|||
ingressResources.forEach(ingressResource => {
|
||||
kubectl.getResource(KubernetesConstants.DiscoveryAndLoadBalancerResource.ingress, ingressResource.name);
|
||||
});
|
||||
// annotate resources
|
||||
let allPods;
|
||||
try {
|
||||
allPods = JSON.parse((kubectl.getAllPods()).stdout);
|
||||
}
|
||||
catch (e) {
|
||||
core.debug("Unable to parse pods; Error: " + e);
|
||||
}
|
||||
annotateResources(deployedManifestFiles, kubectl, resourceTypes, allPods);
|
||||
});
|
||||
}
|
||||
exports.deploy = deploy;
|
||||
|
@ -121,6 +131,18 @@ function checkManifestStability(kubectl, resources) {
|
|||
yield KubernetesManifestUtility.checkManifestStability(kubectl, resources);
|
||||
});
|
||||
}
|
||||
function annotateResources(files, kubectl, resourceTypes, allPods) {
|
||||
const annotateResults = [];
|
||||
annotateResults.push(utility_1.annotateNamespace(kubectl, TaskInputParameters.namespace));
|
||||
annotateResults.push(kubectl.annotateFiles(files, models.workflowAnnotations, true));
|
||||
resourceTypes.forEach(resource => {
|
||||
if (resource.type.toUpperCase() !== models.KubernetesWorkload.pod.toUpperCase()) {
|
||||
utility_1.annotateChildPods(kubectl, resource.type, resource.name, allPods)
|
||||
.forEach(execResult => annotateResults.push(execResult));
|
||||
}
|
||||
});
|
||||
utility_1.checkForErrors(annotateResults, true);
|
||||
}
|
||||
function isCanaryDeploymentStrategy(deploymentStrategy) {
|
||||
return deploymentStrategy != null && deploymentStrategy.toUpperCase() === canaryDeploymentHelper.CANARY_DEPLOYMENT_STRATEGY.toUpperCase();
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getCurrentTime = exports.getRandomInt = exports.sleep = exports.checkForErrors = exports.isEqual = exports.getExecutableExtension = void 0;
|
||||
exports.getCurrentTime = exports.getRandomInt = exports.sleep = exports.annotateNamespace = exports.annotateChildPods = exports.checkForErrors = exports.isEqual = exports.getExecutableExtension = void 0;
|
||||
const os = require("os");
|
||||
const core = require("@actions/core");
|
||||
const constants_1 = require("../constants");
|
||||
function getExecutableExtension() {
|
||||
if (os.type().match(/^Win/)) {
|
||||
return '.exe';
|
||||
|
@ -49,6 +50,41 @@ function checkForErrors(execResults, warnIfError) {
|
|||
}
|
||||
}
|
||||
exports.checkForErrors = checkForErrors;
|
||||
function annotateChildPods(kubectl, resourceType, resourceName, allPods) {
|
||||
const commandExecutionResults = [];
|
||||
let owner = resourceName;
|
||||
if (resourceType.toLowerCase().indexOf('deployment') > -1) {
|
||||
owner = kubectl.getNewReplicaSet(resourceName);
|
||||
}
|
||||
if (!!allPods && !!allPods.items && allPods.items.length > 0) {
|
||||
allPods.items.forEach((pod) => {
|
||||
const owners = pod.metadata.ownerReferences;
|
||||
if (!!owners) {
|
||||
owners.forEach(ownerRef => {
|
||||
if (ownerRef.name === owner) {
|
||||
commandExecutionResults.push(kubectl.annotate('pod', pod.metadata.name, constants_1.workflowAnnotations, true));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return commandExecutionResults;
|
||||
}
|
||||
exports.annotateChildPods = annotateChildPods;
|
||||
function annotateNamespace(kubectl, namespaceName) {
|
||||
let annotate = true;
|
||||
const result = kubectl.getResource('namespace', namespaceName);
|
||||
this.checkForErrors([result]);
|
||||
const annotationsSet = JSON.parse(result.stdout).metadata.annotations;
|
||||
if (!!annotationsSet && !!annotationsSet.runUri && annotationsSet.runUri.indexOf(process.env['GITHUB_REPOSITORY']) == -1) {
|
||||
annotate = false;
|
||||
core.debug(`Skipping 'annotate namespace' as namespace annotated by other workflow`);
|
||||
}
|
||||
if (annotate) {
|
||||
return kubectl.annotate('namespace', namespaceName, constants_1.workflowAnnotations, true);
|
||||
}
|
||||
}
|
||||
exports.annotateNamespace = annotateNamespace;
|
||||
function sleep(timeout) {
|
||||
return new Promise(resolve => setTimeout(resolve, timeout));
|
||||
}
|
||||
|
|
|
@ -24,3 +24,16 @@ export class ServiceTypes {
|
|||
export const deploymentTypes: string[] = ['deployment', 'replicaset', 'daemonset', 'pod', 'statefulset'];
|
||||
export const workloadTypes: string[] = ['deployment', 'replicaset', 'daemonset', 'pod', 'statefulset', 'job', 'cronjob'];
|
||||
export const workloadTypesWithRolloutStatus: string[] = ['deployment', 'daemonset', 'statefulset'];
|
||||
|
||||
export const workflowAnnotations = [
|
||||
`run=${process.env['GITHUB_RUN_ID']}`,
|
||||
`repository=${process.env['GITHUB_REPOSITORY']}`,
|
||||
`workflow=${process.env['GITHUB_WORKFLOW']}`,
|
||||
`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']}`,
|
||||
`branch=${process.env['GITHUB_REF']}`,
|
||||
`deployTimestamp=${Date.now()}`,
|
||||
`provider=GitHub`
|
||||
];
|
|
@ -1,4 +1,4 @@
|
|||
import { ToolRunner, IExecOptions } from "./utilities/tool-runner";
|
||||
import { ToolRunner, IExecOptions, IExecSyncResult } from "./utilities/tool-runner";
|
||||
|
||||
export interface Resource {
|
||||
name: string;
|
||||
|
@ -20,7 +20,7 @@ export class Kubectl {
|
|||
}
|
||||
}
|
||||
|
||||
public apply(configurationPaths: string | string[], force?: boolean) {
|
||||
public apply(configurationPaths: string | string[], force?: boolean): IExecSyncResult {
|
||||
let applyArgs: string[] = ['apply', '-f', this.createInlineArray(configurationPaths)];
|
||||
|
||||
if (!!force) {
|
||||
|
@ -31,13 +31,13 @@ export class Kubectl {
|
|||
return this.execute(applyArgs);
|
||||
}
|
||||
|
||||
public describe(resourceType: string, resourceName: string, silent?: boolean) {
|
||||
public describe(resourceType: string, resourceName: string, silent?: boolean): IExecSyncResult {
|
||||
return this.execute(['describe', resourceType, resourceName], silent);
|
||||
}
|
||||
|
||||
public async getNewReplicaSet(deployment: string) {
|
||||
public getNewReplicaSet(deployment: string) {
|
||||
let newReplicaSet = '';
|
||||
const result = await this.describe('deployment', deployment, true);
|
||||
const result = this.describe('deployment', deployment, true);
|
||||
if (result && result.stdout) {
|
||||
const stdout = result.stdout.split('\n');
|
||||
stdout.forEach((line: string) => {
|
||||
|
@ -50,19 +50,34 @@ export class Kubectl {
|
|||
return newReplicaSet;
|
||||
}
|
||||
|
||||
public getAllPods() {
|
||||
public annotate(resourceType: string, resourceName: string, annotations: string[], overwrite?: boolean): IExecSyncResult {
|
||||
let args = ['annotate', resourceType, resourceName];
|
||||
args = args.concat(annotations);
|
||||
if (!!overwrite) { args.push(`--overwrite`); }
|
||||
return this.execute(args);
|
||||
}
|
||||
|
||||
public annotateFiles(files: string | string[], annotations: string[], overwrite?: boolean): IExecSyncResult {
|
||||
let args = ['annotate'];
|
||||
args = args.concat(['-f', this.createInlineArray(files)]);
|
||||
args = args.concat(annotations);
|
||||
if (!!overwrite) { args.push(`--overwrite`); }
|
||||
return this.execute(args);
|
||||
}
|
||||
|
||||
public getAllPods(): IExecSyncResult {
|
||||
return this.execute(['get', 'pods', '-o', 'json'], true);
|
||||
}
|
||||
|
||||
public getClusterInfo() {
|
||||
public getClusterInfo(): IExecSyncResult {
|
||||
return this.execute(['cluster-info'], true);
|
||||
}
|
||||
|
||||
public checkRolloutStatus(resourceType: string, name: string) {
|
||||
public checkRolloutStatus(resourceType: string, name: string): IExecSyncResult {
|
||||
return this.execute(['rollout', 'status', resourceType + '/' + name]);
|
||||
}
|
||||
|
||||
public getResource(resourceType: string, name: string) {
|
||||
public getResource(resourceType: string, name: string): IExecSyncResult {
|
||||
return this.execute(['get', resourceType + '/' + name, '-o', 'json']);
|
||||
}
|
||||
|
||||
|
@ -87,7 +102,7 @@ export class Kubectl {
|
|||
}
|
||||
|
||||
public executeCommand(customCommand: string, args?: string) {
|
||||
if(!customCommand)
|
||||
if (!customCommand)
|
||||
throw new Error('NullCommandForKubectl');
|
||||
return args ? this.execute([customCommand, args]) : this.execute([customCommand]);
|
||||
}
|
||||
|
|
|
@ -263,7 +263,7 @@ function updateImagePullSecretsInManifestFiles(filePaths: string[], imagePullSec
|
|||
}
|
||||
});
|
||||
});
|
||||
core.debug('New K8s objects after addin imagePullSecrets are :' + JSON.stringify(newObjectsList));
|
||||
core.debug('New K8s objects after adding imagePullSecrets are :' + JSON.stringify(newObjectsList));
|
||||
const newFilePaths = fileHelper.writeObjectsToFile(newObjectsList);
|
||||
return newFilePaths;
|
||||
}
|
||||
|
|
|
@ -90,6 +90,34 @@ export function updateObjectLabels(inputObject: any, newLabels: Map<string, stri
|
|||
}
|
||||
}
|
||||
|
||||
export function updateObjectAnnotations(inputObject: any, newAnnotations: Map<string, string>, override: boolean) {
|
||||
if (!inputObject) {
|
||||
throw ('NullInputObject');
|
||||
}
|
||||
|
||||
if (!inputObject.metadata) {
|
||||
throw ('NullInputObjectMetadata');
|
||||
}
|
||||
|
||||
if (!newAnnotations) {
|
||||
return;
|
||||
}
|
||||
if (override) {
|
||||
inputObject.metadata.annotations = newAnnotations;
|
||||
} else {
|
||||
let existingAnnotations = inputObject.metadata.annotations;
|
||||
if (!existingAnnotations) {
|
||||
existingAnnotations = new Map<string, string>();
|
||||
}
|
||||
|
||||
Object.keys(newAnnotations).forEach(function (key) {
|
||||
existingAnnotations[key] = newAnnotations[key];
|
||||
});
|
||||
|
||||
inputObject.metadata.annotations = existingAnnotations;
|
||||
}
|
||||
}
|
||||
|
||||
export function updateImagePullSecrets(inputObject: any, newImagePullSecrets: string[], override: boolean) {
|
||||
if (!inputObject || !inputObject.spec || !newImagePullSecrets) {
|
||||
return;
|
||||
|
|
|
@ -177,6 +177,7 @@ function addCanaryLabelsAndAnnotations(inputObject: any, type: string) {
|
|||
newLabels[CANARY_VERSION_LABEL] = type;
|
||||
|
||||
helper.updateObjectLabels(inputObject, newLabels, false);
|
||||
helper.updateObjectAnnotations(inputObject, newLabels, false);
|
||||
helper.updateSelectorLabels(inputObject, newLabels, false);
|
||||
|
||||
if (!helper.isServiceEntity(inputObject.kind)) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as core from '@actions/core';
|
||||
import * as yaml from 'js-yaml';
|
||||
import * as canaryDeploymentHelper from './canary-deployment-helper';
|
||||
import * as KubernetesObjectUtility from '../resource-object-utility';
|
||||
|
@ -12,9 +13,11 @@ import * as KubernetesManifestUtility from '../manifest-stability-utility';
|
|||
import * as KubernetesConstants from '../../constants';
|
||||
import { Kubectl, Resource } from '../../kubectl-object-model';
|
||||
import { getUpdatedManifestFiles } from '../manifest-utilities';
|
||||
import { IExecSyncResult } from '../../utilities/tool-runner';
|
||||
|
||||
import { deployPodCanary } from './pod-canary-deployment-helper';
|
||||
import { deploySMICanary } from './smi-canary-deployment-helper';
|
||||
import { checkForErrors } from "../utility";
|
||||
import { checkForErrors, annotateChildPods, annotateNamespace } 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';
|
||||
|
@ -42,6 +45,16 @@ 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 {
|
||||
allPods = JSON.parse((kubectl.getAllPods()).stdout);
|
||||
} catch (e) {
|
||||
core.debug("Unable to parse pods; Error: " + e);
|
||||
}
|
||||
|
||||
annotateResources(deployedManifestFiles, kubectl, resourceTypes, allPods);
|
||||
}
|
||||
|
||||
export function getManifestFiles(manifestFilePaths: string[]): string[] {
|
||||
|
@ -115,6 +128,19 @@ async function checkManifestStability(kubectl: Kubectl, resources: Resource[]):
|
|||
await KubernetesManifestUtility.checkManifestStability(kubectl, resources);
|
||||
}
|
||||
|
||||
function annotateResources(files: string[], kubectl: Kubectl, resourceTypes: Resource[], allPods: any) {
|
||||
const annotateResults: IExecSyncResult[] = [];
|
||||
annotateResults.push(annotateNamespace(kubectl, TaskInputParameters.namespace));
|
||||
annotateResults.push(kubectl.annotateFiles(files, models.workflowAnnotations, true));
|
||||
resourceTypes.forEach(resource => {
|
||||
if (resource.type.toUpperCase() !== models.KubernetesWorkload.pod.toUpperCase()) {
|
||||
annotateChildPods(kubectl, resource.type, resource.name, allPods)
|
||||
.forEach(execResult => annotateResults.push(execResult));
|
||||
}
|
||||
});
|
||||
checkForErrors(annotateResults, true);
|
||||
}
|
||||
|
||||
function isCanaryDeploymentStrategy(deploymentStrategy: string): boolean {
|
||||
return deploymentStrategy != null && deploymentStrategy.toUpperCase() === canaryDeploymentHelper.CANARY_DEPLOYMENT_STRATEGY.toUpperCase();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import * as os from 'os';
|
||||
import * as core from '@actions/core';
|
||||
import { IExecSyncResult } from './tool-runner';
|
||||
import { Kubectl } from '../kubectl-object-model';
|
||||
import { workflowAnnotations } from '../constants';
|
||||
|
||||
export function getExecutableExtension(): string {
|
||||
if (os.type().match(/^Win/)) {
|
||||
|
@ -25,7 +28,7 @@ export function isEqual(str1: string, str2: string, ignoreCase?: boolean): boole
|
|||
}
|
||||
}
|
||||
|
||||
export function checkForErrors(execResults, warnIfError?: boolean) {
|
||||
export function checkForErrors(execResults: IExecSyncResult[], warnIfError?: boolean) {
|
||||
if (execResults.length !== 0) {
|
||||
let stderr = '';
|
||||
execResults.forEach(result => {
|
||||
|
@ -47,6 +50,43 @@ export function checkForErrors(execResults, warnIfError?: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
export function annotateChildPods(kubectl: Kubectl, resourceType: string, resourceName: string, allPods): IExecSyncResult[] {
|
||||
const commandExecutionResults = [];
|
||||
let owner = resourceName;
|
||||
if (resourceType.toLowerCase().indexOf('deployment') > -1) {
|
||||
owner = kubectl.getNewReplicaSet(resourceName);
|
||||
}
|
||||
|
||||
if (!!allPods && !!allPods.items && allPods.items.length > 0) {
|
||||
allPods.items.forEach((pod) => {
|
||||
const owners = pod.metadata.ownerReferences;
|
||||
if (!!owners) {
|
||||
owners.forEach(ownerRef => {
|
||||
if (ownerRef.name === owner) {
|
||||
commandExecutionResults.push(kubectl.annotate('pod', pod.metadata.name, workflowAnnotations, true));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return commandExecutionResults;
|
||||
}
|
||||
|
||||
export function annotateNamespace(kubectl: Kubectl, namespaceName: string): IExecSyncResult {
|
||||
let annotate = true;
|
||||
const result = kubectl.getResource('namespace', namespaceName);
|
||||
this.checkForErrors([result]);
|
||||
const annotationsSet = JSON.parse(result.stdout).metadata.annotations;
|
||||
if (!!annotationsSet && !!annotationsSet.runUri && annotationsSet.runUri.indexOf(process.env['GITHUB_REPOSITORY']) == -1) {
|
||||
annotate = false;
|
||||
core.debug(`Skipping 'annotate namespace' as namespace annotated by other workflow`);
|
||||
}
|
||||
if (annotate) {
|
||||
return kubectl.annotate('namespace', namespaceName, workflowAnnotations, true);
|
||||
}
|
||||
}
|
||||
|
||||
export function sleep(timeout: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, timeout));
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче