Test Cases for deploy (#35)

* Test Cases for deploy

* Testing workflow

* Fixing an issue with test runs on remote

* Fixing an issue with test runs on remote

* Fixing an issue with test runs on remote

* Fixing an issue with test runs on remote

* Fixing an issue with test runs on remote

* Fixing an issue with test runs on remote

* Review Comments fix

* Review comments fix
This commit is contained in:
rgsubh 2020-06-01 09:31:32 +05:30 committed by GitHub
parent 0cca7e9933
commit 468ae744db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 317 additions and 38 deletions

20
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: "build-test"
on: # rebuild any PRs and main branch changes
pull_request:
branches:
- master
- 'releases/*'
push:
branches:
- master
- 'releases/*'
jobs:
build: # make sure build/ci works properly
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: |
npm install
npm build
npm test

1
.gitignore vendored
View File

@ -327,3 +327,4 @@ ASALocalRun/
# MFractors (Xamarin productivity tool) working folder
.mfractor/
node_modules

View File

@ -0,0 +1,16 @@
apiVersion : apps/v1beta1
kind: Deployment
metadata:
name: testapp
spec:
replicas: 1
template:
metadata:
labels:
app: testapp
spec:
containers:
- name: testapp
image: testcr.azurecr.io/testapp
ports:
- containerPort: 80

196
__tests__/run.test.ts Normal file
View File

@ -0,0 +1,196 @@
import * as KubernetesManifestUtility from '../src/utilities/manifest-stability-utility';
import * as KubernetesObjectUtility from '../src/utilities/resource-object-utility';
import * as action from '../src/run';
import * as core from '@actions/core';
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 { Kubectl, Resource } from '../src/kubectl-object-model';
import { getkubectlDownloadURL } from "../src/utilities/kubectl-util";
import { mocked } from 'ts-jest/utils';
var path = require('path');
const coreMock = mocked(core, true);
const ioMock = mocked(io, true);
const toolCacheMock = mocked(toolCache, true);
const fileUtility = mocked(fs, true);
const stableVersionUrl = 'https://storage.googleapis.com/kubernetes-release/release/stable.txt';
var deploymentYaml = "";
beforeAll(() => {
deploymentYaml = fs.readFileSync(path.join(__dirname, 'manifests', 'deployment.yml'), 'utf8');
process.env["KUBECONFIG"] = 'kubeConfig';
})
test("setKubectlPath() - install a particular version", async () => {
const kubectlVersion = 'v1.18.0'
//Mocks
coreMock.getInput = jest.fn().mockReturnValue(kubectlVersion);
toolCacheMock.find = jest.fn().mockReturnValue(undefined);
toolCacheMock.downloadTool = jest.fn().mockReturnValue('downloadpath');
toolCacheMock.cacheFile = jest.fn().mockReturnValue('cachepath');
fileUtility.chmodSync = jest.fn();
//Invoke and assert
await expect(action.run()).resolves.not.toThrow();
expect(toolCacheMock.find).toBeCalledWith('kubectl', kubectlVersion);
expect(toolCacheMock.downloadTool).toBeCalledWith(getkubectlDownloadURL(kubectlVersion));
});
test("setKubectlPath() - install a latest version", async () => {
const kubectlVersion = 'latest'
//Mocks
coreMock.getInput = jest.fn().mockReturnValue(kubectlVersion);
jest.spyOn(fs, 'readFileSync').mockImplementation(() => "");
toolCacheMock.find = jest.fn().mockReturnValue(undefined);
toolCacheMock.downloadTool = jest.fn().mockResolvedValue('');
toolCacheMock.cacheFile = jest.fn().mockReturnValue('cachepath');
fileUtility.chmodSync = jest.fn();
//Invoke and assert
await expect(action.run()).resolves.not.toThrow();
expect(toolCacheMock.find).toBeCalledWith('kubectl', kubectlVersion);
expect(toolCacheMock.downloadTool).toBeCalledWith(stableVersionUrl);
});
test("setKubectlPath() - kubectl version already avilable", async () => {
const kubectlVersion = 'v1.18.0'
//Mock
coreMock.getInput = jest.fn().mockReturnValue(kubectlVersion);
toolCacheMock.find = jest.fn().mockReturnValue('validPath');
toolCacheMock.downloadTool = jest.fn().mockReturnValue('downloadpath');
toolCacheMock.cacheFile = jest.fn().mockReturnValue('cachepath');
fileUtility.chmodSync = jest.fn();
//Invoke and assert
await expect(action.run()).resolves.not.toThrow();
expect(toolCacheMock.find).toBeCalledWith('kubectl', kubectlVersion);
expect(toolCacheMock.downloadTool).toBeCalledTimes(0);
});
test("setKubectlPath() - kubectl version not provided and kubectl avilable on machine", async () => {
//Mock
coreMock.getInput = jest.fn().mockReturnValue(undefined);
ioMock.which = jest.fn().mockReturnValue('validPath');
//Invoke and assert
await expect(action.run()).resolves.not.toThrow();
expect(ioMock.which).toBeCalledWith('kubectl', false);
expect(toolCacheMock.downloadTool).toBeCalledTimes(0);
});
test("setKubectlPath() - kubectl version not provided and kubectl not avilable on machine", async () => {
//Mock
coreMock.getInput = jest.fn().mockReturnValue(undefined);
ioMock.which = jest.fn().mockReturnValue(undefined);
toolCacheMock.findAllVersions = jest.fn().mockReturnValue(undefined);
//Invoke and assert
await expect(action.run()).rejects.toThrowError();
expect(ioMock.which).toBeCalledWith('kubectl', false);
});
test("run() - action not provided", async () => {
const kubectlVersion = 'v1.18.0'
coreMock.getInput = jest.fn().mockImplementation((name) => {
if (name == 'action') {
return undefined;
}
return kubectlVersion;
});
coreMock.setFailed = jest.fn();
//Mocks
toolCacheMock.find = jest.fn().mockReturnValue(undefined);
toolCacheMock.downloadTool = jest.fn().mockReturnValue('downloadpath');
toolCacheMock.cacheFile = jest.fn().mockReturnValue('cachepath');
fileUtility.chmodSync = jest.fn();
//Invoke and assert
await expect(action.run()).resolves.not.toThrow();
expect(coreMock.setFailed).toBeCalledWith('Not a valid action. The allowed actions are deploy, promote, reject');
});
test("run() - deploy - Manifiest not provided", async () => {
//Mocks
const kubectlVersion = 'v1.18.0'
coreMock.getInput = jest.fn().mockImplementation((name) => {
if (name == 'manifests') {
return undefined;
}
if (name == 'action') {
return 'deploy';
}
return kubectlVersion;
});
coreMock.setFailed = jest.fn();
toolCacheMock.find = jest.fn().mockReturnValue(undefined);
toolCacheMock.downloadTool = jest.fn().mockReturnValue('downloadpath');
toolCacheMock.cacheFile = jest.fn().mockReturnValue('cachepath');
fileUtility.chmodSync = jest.fn();
//Invoke and assert
await expect(action.run()).rejects.toThrowError();
expect(coreMock.setFailed).toBeCalledWith('No manifests supplied to deploy');
});
test("deployment - deploy() - Invokes with no manifestfiles", async () => {
const kubeCtl: jest.Mocked<Kubectl> = new Kubectl("") as any;
//Invoke and assert
await expect(deployment.deploy(kubeCtl, [], undefined)).rejects.toThrowError('ManifestFileNotFound');
});
test("run() - deploy", async () => {
const kubectlVersion = 'v1.18.0'
//Mocks
coreMock.getInput = jest.fn().mockImplementation((name) => {
if (name == 'manifests') {
return 'manifests/deployment.yaml';
}
if (name == 'action') {
return 'deploy';
}
if (name == 'strategy') {
return undefined;
}
return kubectlVersion;
});
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();
const deploySpy = jest.spyOn(deployment, 'deploy').mockImplementation();
//Invoke and assert
await expect(action.run()).resolves.not.toThrow();
expect(deploySpy).toBeCalledWith({ "ignoreSSLErrors": false, "kubectlPath": 'validPath', "namespace": "v1.18.0" }, ['manifests/deployment.yaml'], undefined);
deploySpy.mockRestore();
});
test("deployment - deploy() - Invokes with manifestfiles", async () => {
const KubernetesManifestUtilityMock = mocked(KubernetesManifestUtility, true);
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("");
KubernetesManifestUtilityMock.checkManifestStability = jest.fn().mockReturnValue("");
const readFileSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => deploymentYaml);
//Invoke and assert
await expect(deployment.deploy(kubeCtl, ['manifests/deployment.yaml'], undefined)).resolves.not.toThrowError();
expect(readFileSpy).toBeCalledWith("manifests/deployment.yaml");
expect(kubeCtl.getResource).toBeCalledWith("ingress", "AppName");
});

10
jest.config.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
}

View File

@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.promote = void 0;
const core = require("@actions/core");
const deploymentHelper = require("../utilities/strategy-helpers/deployment-helper");
const canaryDeploymentHelper = require("../utilities/strategy-helpers/canary-deployment-helper");

View File

@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.reject = void 0;
const core = require("@actions/core");
const canaryDeploymentHelper = require("../utilities/strategy-helpers/canary-deployment-helper");
const SMICanaryDeploymentHelper = require("../utilities/strategy-helpers/smi-canary-deployment-helper");

View File

@ -1,26 +1,36 @@
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
class KubernetesWorkload {
}
exports.workloadTypesWithRolloutStatus = exports.workloadTypes = exports.deploymentTypes = exports.ServiceTypes = exports.DiscoveryAndLoadBalancerResource = exports.KubernetesWorkload = void 0;
let KubernetesWorkload = /** @class */ (() => {
class KubernetesWorkload {
}
KubernetesWorkload.pod = 'Pod';
KubernetesWorkload.replicaset = 'Replicaset';
KubernetesWorkload.deployment = 'Deployment';
KubernetesWorkload.statefulSet = 'StatefulSet';
KubernetesWorkload.daemonSet = 'DaemonSet';
KubernetesWorkload.job = 'job';
KubernetesWorkload.cronjob = 'cronjob';
return KubernetesWorkload;
})();
exports.KubernetesWorkload = KubernetesWorkload;
KubernetesWorkload.pod = 'Pod';
KubernetesWorkload.replicaset = 'Replicaset';
KubernetesWorkload.deployment = 'Deployment';
KubernetesWorkload.statefulSet = 'StatefulSet';
KubernetesWorkload.daemonSet = 'DaemonSet';
KubernetesWorkload.job = 'job';
KubernetesWorkload.cronjob = 'cronjob';
class DiscoveryAndLoadBalancerResource {
}
let DiscoveryAndLoadBalancerResource = /** @class */ (() => {
class DiscoveryAndLoadBalancerResource {
}
DiscoveryAndLoadBalancerResource.service = 'service';
DiscoveryAndLoadBalancerResource.ingress = 'ingress';
return DiscoveryAndLoadBalancerResource;
})();
exports.DiscoveryAndLoadBalancerResource = DiscoveryAndLoadBalancerResource;
DiscoveryAndLoadBalancerResource.service = 'service';
DiscoveryAndLoadBalancerResource.ingress = 'ingress';
class ServiceTypes {
}
let ServiceTypes = /** @class */ (() => {
class ServiceTypes {
}
ServiceTypes.loadBalancer = 'LoadBalancer';
ServiceTypes.nodePort = 'NodePort';
ServiceTypes.clusterIP = 'ClusterIP';
return ServiceTypes;
})();
exports.ServiceTypes = ServiceTypes;
ServiceTypes.loadBalancer = 'LoadBalancer';
ServiceTypes.nodePort = 'NodePort';
ServiceTypes.clusterIP = 'ClusterIP';
exports.deploymentTypes = ['deployment', 'replicaset', 'daemonset', 'pod', 'statefulset'];
exports.workloadTypes = ['deployment', 'replicaset', 'daemonset', 'pod', 'statefulset', 'job', 'cronjob'];
exports.workloadTypesWithRolloutStatus = ['deployment', 'daemonset', 'statefulset'];

View File

@ -1,5 +1,6 @@
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.args = exports.baselineAndCanaryReplicas = exports.trafficSplitMethod = exports.deploymentStrategy = exports.canaryPercentage = exports.manifests = exports.imagePullSecrets = exports.containers = exports.namespace = void 0;
const core = require("@actions/core");
exports.namespace = core.getInput('namespace');
exports.containers = core.getInput('images').split('\n');

View File

@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Kubectl = void 0;
const tool_runner_1 = require("./utilities/tool-runner");
class Kubectl {
constructor(kubectlPath, namespace, ignoreSSLErrors) {

View File

@ -9,16 +9,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const toolCache = require("@actions/tool-cache");
exports.run = void 0;
const core = require("@actions/core");
const io = require("@actions/io");
const path = require("path");
const utility_1 = require("./utilities/utility");
const toolCache = require("@actions/tool-cache");
const kubectl_util_1 = require("./utilities/kubectl-util");
const utility_1 = require("./utilities/utility");
const kubectl_object_model_1 = require("./kubectl-object-model");
const deployment_helper_1 = require("./utilities/strategy-helpers/deployment-helper");
const promote_1 = require("./actions/promote");
const reject_1 = require("./actions/reject");
const kubectl_object_model_1 = require("./kubectl-object-model");
let kubectlPath = "";
function setKubectlPath() {
return __awaiter(this, void 0, void 0, function* () {
@ -68,7 +69,7 @@ function run() {
namespace = 'default';
}
let action = core.getInput('action');
let manifests = manifestsInput.split('\n');
let manifests = manifestsInput ? manifestsInput.split('\n') : undefined;
if (action === 'deploy') {
let strategy = core.getInput('strategy');
console.log("strategy: ", strategy);
@ -85,4 +86,5 @@ function run() {
}
});
}
exports.run = run;
run().catch(core.setFailed);

View File

@ -1,5 +1,6 @@
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeManifestToFile = exports.writeObjectsToFile = exports.assertFileExists = exports.ensureDirExists = exports.getNewUserDirPath = exports.getTempDirectory = void 0;
const fs = require("fs");
const path = require("path");
const core = require("@actions/core");

View File

@ -9,12 +9,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTrafficSplitAPIVersion = exports.downloadKubectl = exports.getStableKubectlVersion = exports.getkubectlDownloadURL = void 0;
const core = require("@actions/core");
const fs = require("fs");
const os = require("os");
const path = require("path");
const util = require("util");
const fs = require("fs");
const toolCache = require("@actions/tool-cache");
const core = require("@actions/core");
const util = require("util");
const kubectlToolName = 'kubectl';
const stableKubectlVersion = 'v1.15.0';
const stableVersionUrl = 'https://storage.googleapis.com/kubernetes-release/release/stable.txt';
@ -36,6 +37,7 @@ function getkubectlDownloadURL(version) {
return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/windows/amd64/kubectl.exe', version);
}
}
exports.getkubectlDownloadURL = getkubectlDownloadURL;
function getStableKubectlVersion() {
return __awaiter(this, void 0, void 0, function* () {
return toolCache.downloadTool(stableVersionUrl).then((downloadPath) => {

View File

@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkPodStatus = exports.checkManifestStability = void 0;
const core = require("@actions/core");
const utils = require("./utility");
const KubernetesConstants = require("../constants");

View File

@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isWorkloadEntity = exports.updateImagePullSecrets = exports.updateContainerImagesInManifestFiles = exports.substituteImageNameInSpecFile = exports.getDeleteCmdArgs = exports.createKubectlArgs = exports.getKubectl = exports.getManifestFiles = void 0;
const core = require("@actions/core");
const kubectlutility = require("./kubectl-util");
const io = require("@actions/io");

View File

@ -1,5 +1,6 @@
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.getResources = exports.updateSelectorLabels = exports.updateSpecLabels = exports.updateImageDetails = exports.updateImagePullSecrets = exports.updateObjectLabels = exports.getReplicaCount = exports.isServiceEntity = exports.isWorkloadEntity = exports.isDeploymentEntity = void 0;
const fs = require("fs");
const core = require("@actions/core");
const yaml = require("js-yaml");

View File

@ -1,5 +1,6 @@
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.getStableResourceName = exports.getBaselineResourceName = exports.getCanaryResourceName = exports.isSMICanaryStrategy = exports.isCanaryDeploymentStrategy = exports.fetchResource = exports.fetchCanaryResource = exports.getNewCanaryResource = exports.getNewBaselineResource = exports.getStableResource = exports.isResourceMarkedAsStable = exports.markResourceAsStable = exports.deleteCanaryDeployment = exports.STABLE_LABEL_VALUE = exports.STABLE_SUFFIX = exports.CANARY_LABEL_VALUE = exports.BASELINE_LABEL_VALUE = exports.CANARY_VERSION_LABEL = exports.TRAFFIC_SPLIT_STRATEGY = exports.CANARY_DEPLOYMENT_STRATEGY = void 0;
const fs = require("fs");
const yaml = require("js-yaml");
const core = require("@actions/core");

View File

@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.deploy = void 0;
const fs = require("fs");
const core = require("@actions/core");
const yaml = require("js-yaml");

View File

@ -1,5 +1,6 @@
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.deployPodCanary = void 0;
const core = require("@actions/core");
const fs = require("fs");
const yaml = require("js-yaml");

View File

@ -1,5 +1,6 @@
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.redirectTrafficToStableDeployment = exports.redirectTrafficToCanaryDeployment = exports.deploySMICanary = void 0;
const core = require("@actions/core");
const fs = require("fs");
const yaml = require("js-yaml");

View File

@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isEqual = exports.StringComparer = void 0;
var StringComparer;
(function (StringComparer) {
StringComparer[StringComparer["Ordinal"] = 0] = "Ordinal";

View File

@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ToolRunner = void 0;
const os = require("os");
const events = require("events");
const child = require("child_process");

View File

@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCurrentTime = exports.getRandomInt = exports.sleep = exports.checkForErrors = exports.isEqual = exports.getExecutableExtension = void 0;
const os = require("os");
const core = require("@actions/core");
function getExecutableExtension() {

View File

@ -4,7 +4,8 @@
"author": "Deepak Sattiraju",
"license": "MIT",
"scripts": {
"build": "tsc --outDir ./lib --rootDir ./src"
"build": "tsc --outDir ./lib --rootDir ./src",
"test": "jest"
},
"dependencies": {
"@actions/tool-cache": "^1.0.0",
@ -14,6 +15,10 @@
"js-yaml": "3.13.1"
},
"devDependencies": {
"@types/node": "^12.0.10"
"@types/node": "^12.0.10",
"jest": "^25.0.0",
"@types/jest": "^25.2.2",
"ts-jest": "^25.5.1",
"typescript": "^3.9.2"
}
}

View File

@ -1,14 +1,15 @@
import * as toolCache from '@actions/tool-cache';
import * as core from '@actions/core';
import * as io from '@actions/io';
import * as path from 'path';
import * as toolCache from '@actions/tool-cache';
import { getExecutableExtension, isEqual } from "./utilities/utility";
import { downloadKubectl, getStableKubectlVersion } from "./utilities/kubectl-util";
import { getExecutableExtension, isEqual } from "./utilities/utility";
import { Kubectl } from './kubectl-object-model';
import { deploy } from './utilities/strategy-helpers/deployment-helper';
import { promote } from './actions/promote';
import { reject } from './actions/reject';
import { Kubectl } from './kubectl-object-model';
let kubectlPath = "";
@ -45,7 +46,7 @@ function checkClusterContext() {
}
}
async function run() {
export async function run() {
checkClusterContext();
await setKubectlPath();
let manifestsInput = core.getInput('manifests');
@ -57,7 +58,7 @@ async function run() {
namespace = 'default';
}
let action = core.getInput('action');
let manifests = manifestsInput.split('\n');
let manifests = manifestsInput ? manifestsInput.split('\n') : undefined;
if (action === 'deploy') {
let strategy = core.getInput('strategy');

View File

@ -1,10 +1,10 @@
import * as core from '@actions/core';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as util from 'util';
import * as fs from 'fs';
import * as toolCache from '@actions/tool-cache';
import * as core from '@actions/core';
import * as util from 'util';
import { Kubectl } from '../kubectl-object-model';
const kubectlToolName = 'kubectl';
@ -19,7 +19,7 @@ function getExecutableExtension(): string {
return '';
}
function getkubectlDownloadURL(version: string): string {
export function getkubectlDownloadURL(version: string): string {
switch (os.type()) {
case 'Linux':
return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/linux/amd64/kubectl', version);

View File

@ -4,6 +4,7 @@
"module": "commonjs"
},
"exclude": [
"node_modules"
"node_modules",
"__tests__"
]
}