* added blue green strategy

* Addressed review comments

* addressed pr comments

* updated names in test

* addressed final pr comments
This commit is contained in:
Sundar 2020-07-13 08:59:05 +05:30 коммит произвёл GitHub
Родитель c9b54fdae2
Коммит b4bc3003e8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
26 изменённых файлов: 2893 добавлений и 175 удалений

Просмотреть файл

@ -0,0 +1,783 @@
import * as fs from 'fs';
import * as inputParam from '../src/input-parameters';
import * as fileHelper from '../src/utilities/files-helper';
import {
Kubectl,
} from '../src/kubectl-object-model';
import {
mocked
} from 'ts-jest/utils';
import * as kubectlUtils from '../src/utilities/kubectl-util';
var path = require('path');
const inputParamMock = mocked(inputParam, true);
var deploymentYaml = "";
import * as blueGreenHelper from '../src/utilities/strategy-helpers/blue-green-helper';
import * as blueGreenHelperService from '../src/utilities/strategy-helpers/service-blue-green-helper';
import * as blueGreenHelperIngress from '../src/utilities/strategy-helpers/ingress-blue-green-helper';
import * as blueGreenHelperSMI from '../src/utilities/strategy-helpers/smi-blue-green-helper';
beforeAll(() => {
deploymentYaml = fs.readFileSync(path.join(__dirname, 'manifests', 'bg.yml'), 'utf8');
process.env["KUBECONFIG"] = 'kubeConfig';
});
test("deployBlueGreen - checks if deployment can be done, then deploys", () => {
const fileHelperMock = mocked(fileHelper, true);
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
let temp = {
stdout: undefined
};
fileHelperMock.writeObjectsToFile = jest.fn().mockReturnValue('hello');
kubeCtl.apply = jest.fn().mockReturnValue('');
kubeCtl.getResource = jest.fn().mockReturnValue(JSON.parse(JSON.stringify(temp)));
const readFileSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => deploymentYaml);
//Invoke and assert
expect(blueGreenHelperService.deployBlueGreenService(kubeCtl, ['manifests/bg.yaml'])).toMatchObject({
"newFilePaths": "hello",
"result": ""
});
expect(readFileSpy).toBeCalledWith("manifests/bg.yaml");
expect(fileHelperMock.writeObjectsToFile).toBeCalled();
expect(kubeCtl.apply).toBeCalled();
});
test("blueGreenPromote - checks if in deployed state and then promotes", () => {
const fileHelperMock = mocked(fileHelper, true);
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
let temp = {
stdout: JSON.stringify({
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "testservice"
},
"spec": {
"selector": {
"app": "testapp",
"k8s.deploy.color": "green"
},
"ports": [{
"protocol": "TCP",
"port": 80,
"targetPort": 80
}]
}
})
};
fileHelperMock.writeObjectsToFile = jest.fn().mockReturnValue('hello');
kubeCtl.apply = jest.fn().mockReturnValue('');
kubeCtl.getResource = jest.fn().mockReturnValue(JSON.parse(JSON.stringify(temp)));
const readFileSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => deploymentYaml);
//Invoke and assert
const manifestObjects = blueGreenHelper.getManifestObjects(['manifests/bg.yaml']);
expect(blueGreenHelperService.promoteBlueGreenService(kubeCtl, manifestObjects)).toMatchObject({});
expect(readFileSpy).toBeCalledWith("manifests/bg.yaml");
expect(kubeCtl.apply).toBeCalledWith("hello");
expect(fileHelperMock.writeObjectsToFile).toBeCalled();
});
test("blueGreenReject - routes servcies to old deployment and deletes new deployment", () => {
const fileHelperMock = mocked(fileHelper, true);
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
let temp = {
stdout: JSON.stringify({
"apiVersion": "apps/v1beta1",
"kind": "Deployment",
"metadata": {
"name": "testapp",
"labels": {
"k8s.deploy.color": "none"
}
},
"spec": {
"selector": {
"matchLabels": {
"app": "testapp",
"k8s.deploy.color": "none"
}
},
"replicas": 1,
"template": {
"metadata": {
"labels": {
"app": "testapp",
"k8s.deploy.color": "none"
}
},
"spec": {
"containers": [{
"name": "testapp",
"image": "testcr.azurecr.io/testapp",
"ports": [{
"containerPort": 80
}]
}]
}
}
}
})
};
kubeCtl.delete = jest.fn().mockReturnValue('');
fileHelperMock.writeObjectsToFile = jest.fn().mockReturnValue('hello');
kubeCtl.apply = jest.fn().mockReturnValue('');
kubeCtl.getResource = jest.fn().mockReturnValue(JSON.parse(JSON.stringify(temp)));
const readFileSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => deploymentYaml);
//Invoke and assert
expect(blueGreenHelperService.rejectBlueGreenService(kubeCtl, ['manifests/bg.yaml'])).toMatchObject({});
expect(kubeCtl.delete).toBeCalledWith(["Deployment", "testapp-green"]);
expect(readFileSpy).toBeCalledWith("manifests/bg.yaml");
expect(fileHelperMock.writeObjectsToFile).toBeCalled();
expect(kubeCtl.getResource).toBeCalledWith("Deployment", "testapp");
});
test("blueGreenReject - deletes services if old deployment does not exist", () => {
const fileHelperMock = mocked(fileHelper, true);
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
let temp = {
stdout: undefined
};
fileHelperMock.writeObjectsToFile = jest.fn().mockReturnValue('hello');
kubeCtl.apply = jest.fn().mockReturnValue('');
kubeCtl.delete = jest.fn().mockReturnValue('');
kubeCtl.getResource = jest.fn().mockReturnValue(JSON.parse(JSON.stringify(temp)));
const readFileSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => deploymentYaml);
//Invoke and assert
expect(blueGreenHelperService.rejectBlueGreenService(kubeCtl, ['manifests/bg.yaml'])).toMatchObject({});
expect(kubeCtl.delete).toBeCalledWith(["Deployment", "testapp-green"]);
expect(kubeCtl.delete).toBeCalledWith(["Service", "testservice"]);
expect(readFileSpy).toBeCalledWith("manifests/bg.yaml");
expect(fileHelperMock.writeObjectsToFile).toBeCalled();
expect(kubeCtl.getResource).toBeCalledWith("Deployment", "testapp");
});
test("isIngressRoute() - returns true if route-method is ingress", () => {
// default is service
expect(blueGreenHelper.isIngressRoute()).toBeFalsy();
});
test("isIngressRoute() - returns true if route-method is ingress", () => {
inputParamMock.routeMethod = 'ingress'
expect(blueGreenHelper.isIngressRoute()).toBeTruthy();
});
test("deployBlueGreenIngress - creates deployments, services and other non ingress objects", () => {
const fileHelperMock = mocked(fileHelper, true);
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
let temp = {
stdout: undefined
};
fileHelperMock.writeObjectsToFile = jest.fn().mockReturnValue('hello');
kubeCtl.apply = jest.fn().mockReturnValue('');
kubeCtl.getResource = jest.fn().mockReturnValue(JSON.parse(JSON.stringify(temp)));
const readFileSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => deploymentYaml);
//Invoke and assert
expect(blueGreenHelperIngress.deployBlueGreenIngress(kubeCtl, ['manifests/bg.yaml'])).toMatchObject({
"newFilePaths": "hello",
"result": ""
});
expect(readFileSpy).toBeCalledWith("manifests/bg.yaml");
expect(kubeCtl.apply).toBeCalledWith("hello");
});
test("blueGreenPromoteIngress - checks if in deployed state and then promotes ingress", () => {
const fileHelperMock = mocked(fileHelper, true);
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
fileHelperMock.writeObjectsToFile = jest.fn().mockReturnValue('hello');
kubeCtl.apply = jest.fn().mockReturnValue('');
const readFileSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => deploymentYaml);
let temp = {
stdout: JSON.stringify({
"apiVersion": "networking.k8s.io/v1beta1",
"kind": "Ingress",
"metadata": {
"name": "testingress",
"labels": {
"k8s.deploy.color": "green"
},
"annotations": {
"nginx.ingress.kubernetes.io/rewrite-target": "/"
}
},
"spec": {
"rules": [{
"http": {
"paths": [{
"path": "/testpath",
"pathType": "Prefix",
"backend": {
"serviceName": "testservice-green",
"servicePort": 80
}
}]
}
}]
}
})
};
kubeCtl.getResource = jest.fn().mockReturnValue(JSON.parse(JSON.stringify(temp)));
const manifestObjects = blueGreenHelper.getManifestObjects(['manifests/bg.yaml']);
//Invoke and assert
expect(blueGreenHelperIngress.promoteBlueGreenIngress(kubeCtl, manifestObjects)).toMatchObject({});
expect(readFileSpy).toBeCalledWith("manifests/bg.yaml");
expect(kubeCtl.apply).toBeCalledWith("hello");
});
test("blueGreenRejectIngress - routes ingress to stable services and deletes new deployments and services", () => {
const fileHelperMock = mocked(fileHelper, true);
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
fileHelperMock.writeObjectsToFile = jest.fn().mockReturnValue('hello');
kubeCtl.apply = jest.fn().mockReturnValue('');
kubeCtl.delete = jest.fn().mockReturnValue('');
const readFileSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => deploymentYaml);
//Invoke and assert
expect(blueGreenHelperIngress.rejectBlueGreenIngress(kubeCtl, ['manifests/bg.yaml'])).toMatchObject({});
expect(kubeCtl.delete).toBeCalledWith(["Deployment", "testapp-green"]);
expect(kubeCtl.delete).toBeCalledWith(["Service", "testservice-green"]);
expect(readFileSpy).toBeCalledWith("manifests/bg.yaml");
expect(fileHelperMock.writeObjectsToFile).toBeCalled();
});
test("isSMIRoute() - returns true if route-method is smi", () => {
inputParamMock.routeMethod = 'smi'
expect(blueGreenHelper.isSMIRoute()).toBeTruthy();
});
test("isSMIRoute() - returns true if route-method is smi", () => {
inputParamMock.routeMethod = 'ingress'
expect(blueGreenHelper.isSMIRoute()).toBeFalsy();
});
test("deployBlueGreenSMI - checks if deployment can be done, then deploys along this auxiliary services and trafficsplit", () => {
const fileHelperMock = mocked(fileHelper, true);
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
let temp = {
stdout: undefined
};
fileHelperMock.writeObjectsToFile = jest.fn().mockReturnValue('hello');
kubeCtl.apply = jest.fn().mockReturnValue('');
kubeCtl.getResource = jest.fn().mockReturnValue(JSON.parse(JSON.stringify(temp)));
const readFileSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => deploymentYaml);
const kubectlUtilsMock = mocked(kubectlUtils, true);
kubectlUtilsMock.getTrafficSplitAPIVersion = jest.fn().mockReturnValue('split.smi-spec.io/v1alpha2');
//Invoke and assert
expect(blueGreenHelperSMI.deployBlueGreenSMI(kubeCtl, ['manifests/bg.yaml'])).toMatchObject({
"newFilePaths": "hello",
"result": ""
});
expect(readFileSpy).toBeCalledWith("manifests/bg.yaml");
expect(fileHelperMock.writeObjectsToFile).toBeCalled();
});
test("blueGreenPromoteSMI - checks weights of trafficsplit and then deploys", () => {
const fileHelperMock = mocked(fileHelper, true);
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
fileHelperMock.writeObjectsToFile = jest.fn().mockReturnValue('hello');
kubeCtl.apply = jest.fn().mockReturnValue('');
const readFileSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => deploymentYaml);
let temp = {
stdout: JSON.stringify({
"apiVersion": "split.smi-spec.io/v1alpha2",
"kind": "TrafficSplit",
"metadata": {
"name": "testservice-rollout"
},
"spec": {
"service": "testservice",
"backends": [{
"service": "testservice-stable",
"weight": 0
},
{
"service": "testservice-green",
"weight": 100
}
]
}
})
};
kubeCtl.getResource = jest.fn().mockReturnValue(JSON.parse(JSON.stringify(temp)));
const manifestObjects = blueGreenHelper.getManifestObjects(['manifests/bg.yaml']);
//Invoke and assert
expect(blueGreenHelperSMI.promoteBlueGreenSMI(kubeCtl, manifestObjects)).toMatchObject({});
expect(readFileSpy).toBeCalledWith("manifests/bg.yaml");
});
test("blueGreenRejectSMI - routes servcies to old deployment and deletes new deployment, auxiliary services and trafficsplit", () => {
const fileHelperMock = mocked(fileHelper, true);
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
let temp = {
stdout: JSON.stringify({
"apiVersion": "apps/v1beta1",
"kind": "Deployment",
"metadata": {
"name": "testapp",
"labels": {
"k8s.deploy.color": "none"
}
},
"spec": {
"selector": {
"matchLabels": {
"app": "testapp",
"k8s.deploy.color": "none"
}
},
"replicas": 1,
"template": {
"metadata": {
"labels": {
"app": "testapp",
"k8s.deploy.color": "none"
}
},
"spec": {
"containers": [{
"name": "testapp",
"image": "testcr.azurecr.io/testapp",
"ports": [{
"containerPort": 80
}]
}]
}
}
}
})
};
kubeCtl.delete = jest.fn().mockReturnValue('');
fileHelperMock.writeObjectsToFile = jest.fn().mockReturnValue('hello');
kubeCtl.apply = jest.fn().mockReturnValue('');
kubeCtl.getResource = jest.fn().mockReturnValue(JSON.parse(JSON.stringify(temp)));
const readFileSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => deploymentYaml);
//Invoke and assert
expect(blueGreenHelperSMI.rejectBlueGreenSMI(kubeCtl, ['manifests/bg.yaml'])).toMatchObject({});
expect(kubeCtl.delete).toBeCalledWith(["Deployment", "testapp-green"]);
expect(kubeCtl.delete).toBeCalledWith(["Service", "testservice-green"]);
expect(kubeCtl.delete).toBeCalledWith(["Service", "testservice-stable"]);
expect(kubeCtl.delete).toBeCalledWith(["TrafficSplit", "testservice-trafficsplit"]);
expect(readFileSpy).toBeCalledWith("manifests/bg.yaml");
expect(kubeCtl.getResource).toBeCalledWith("Deployment", "testapp");
});
test("blueGreenRejectSMI - deletes service if stable deployment doesn't exist", () => {
const fileHelperMock = mocked(fileHelper, true);
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
let temp = {
stdout: undefined
};
kubeCtl.delete = jest.fn().mockReturnValue('');
fileHelperMock.writeObjectsToFile = jest.fn().mockReturnValue('hello');
kubeCtl.apply = jest.fn().mockReturnValue('');
kubeCtl.getResource = jest.fn().mockReturnValue(JSON.parse(JSON.stringify(temp)));
const readFileSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => deploymentYaml);
//Invoke and assert
expect(blueGreenHelperSMI.rejectBlueGreenSMI(kubeCtl, ['manifests/bg.yaml'])).toMatchObject({});
expect(kubeCtl.delete).toBeCalledWith(["Deployment", "testapp-green"]);
expect(kubeCtl.delete).toBeCalledWith(["Service", "testservice"]);
expect(kubeCtl.delete).toBeCalledWith(["Service", "testservice-green"]);
expect(kubeCtl.delete).toBeCalledWith(["Service", "testservice-stable"]);
expect(kubeCtl.delete).toBeCalledWith(["TrafficSplit", "testservice-trafficsplit"]);
expect(readFileSpy).toBeCalledWith("manifests/bg.yaml");
expect(kubeCtl.getResource).toBeCalledWith("Deployment", "testapp");
});
// other functions and branches
test("blueGreenRouteIngress - routes to green services in nextlabel is green", () => {
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
const fileHelperMock = mocked(fileHelper, true);
const ingEntList = [{
"apiVersion": "networking.k8s.io/v1beta1",
"kind": "Ingress",
"metadata": {
"name": "test-ingress",
"annotations": {
"nginx.ingress.kubernetes.io/rewrite-target": "/"
},
},
"spec": {
"rules": [{
"http": {
"paths": [{
"path": "/testpath",
"pathType": "Prefix",
"backend": {
"serviceName": "testservice",
"servicePort": 80
}
},
{
"path": "/testpath",
"pathType": "Prefix",
"backend": {
"serviceName": "random",
"servicePort": 80
}
}
]
}
}]
}
},
{
"apiVersion": "networking.k8s.io/v1beta1",
"kind": "Ingress",
"metadata": {
"name": "test-ingress",
"annotations": {
"nginx.ingress.kubernetes.io/rewrite-target": "/"
},
},
"spec": {
"rules": [{
"http": {
"paths": [{
"path": "/testpath",
"pathType": "Prefix",
"backend": {
"serviceName": "random",
"servicePort": 80
}
}]
}
}]
}
}
];
const serEntList = [{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "testservice"
},
"spec": {
"selector": {
"app": "testapp",
},
"ports": [{
"protocol": "TCP",
"port": 80,
"targetPort": 80
}]
}
}];
let serviceEntityMap = new Map<string, string>();
serviceEntityMap.set('testservice', 'testservice-green');
fileHelperMock.writeObjectsToFile = jest.fn().mockReturnValue('hello');
kubeCtl.apply = jest.fn().mockReturnValue('');
//Invoke and assert
expect(blueGreenHelperIngress.routeBlueGreenIngress(kubeCtl, 'green', serviceEntityMap, serEntList, ingEntList));
expect(kubeCtl.apply).toBeCalled();
expect(fileHelperMock.writeObjectsToFile).toBeCalled();
});
test("shouldWePromoteIngress - throws if routed ingress does not exist", () => {
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
let temp = {
stdout: undefined
}
const ingEntList = [{
"apiVersion": "networking.k8s.io/v1beta1",
"kind": "Ingress",
"metadata": {
"name": "test-ingress",
"annotations": {
"nginx.ingress.kubernetes.io/rewrite-target": "/"
}
},
"spec": {
"rules": [{
"http": {
"paths": [{
"path": "/testpath",
"pathType": "Prefix",
"backend": {
"serviceName": "testservice",
"servicePort": 80
}
}]
}
}]
}
}];
let serviceEntityMap = new Map<string, string>();
serviceEntityMap.set('testservice', 'testservice-green');
kubeCtl.getResource = jest.fn().mockReturnValue(JSON.parse(JSON.stringify(temp)));
//Invoke and assert
expect(blueGreenHelperIngress.validateIngressesState(kubeCtl, ingEntList, serviceEntityMap)).toBeFalsy();
});
test("validateTrafficSplitState - throws if trafficsplit in wrong state", () => {
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
let temp = {
stdout: JSON.stringify({
"apiVersion": "split.smi-spec.io/v1alpha2",
"kind": "TrafficSplit",
"metadata": {
"name": "testservice-trafficsplit"
},
"spec": {
"service": "testservice",
"backends": [{
"service": "testservice-stable",
"weight": 100
},
{
"service": "testservice-green",
"weight": 0
}
]
}
})
}
const depEntList = [{
"apiVersion": "apps/v1beta1",
"kind": "Deployment",
"metadata": {
"name": "testapp",
},
"spec": {
"selector": {
"matchLabels": {
"app": "testapp",
}
},
"replicas": 1,
"template": {
"metadata": {
"labels": {
"app": "testapp",
}
},
"spec": {
"containers": [{
"name": "testapp",
"image": "testcr.azurecr.io/testapp",
"ports": [{
"containerPort": 80
}]
}]
}
}
}
}];
const serEntList = [{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "testservice"
},
"spec": {
"selector": {
"app": "testapp",
},
"ports": [{
"protocol": "TCP",
"port": 80,
"targetPort": 80
}]
}
}];
kubeCtl.getResource = jest.fn().mockReturnValue(JSON.parse(JSON.stringify(temp)));
//Invoke and assert
expect(blueGreenHelperSMI.validateTrafficSplitsState(kubeCtl, depEntList, serEntList)).toBeFalsy();
});
test("validateTrafficSplitState - throws if trafficsplit in wrong state", () => {
const kubeCtl: jest.Mocked < Kubectl > = new Kubectl("") as any;
let temp = {
stdout: JSON.stringify({
"apiVersion": "split.smi-spec.io/v1alpha2",
"kind": "TrafficSplit",
"metadata": {
"name": "testservice-trafficsplit"
},
"spec": {
"service": "testservice",
"backends": [{
"service": "testservice-stable",
"weight": 0
},
{
"service": "testservice-green",
"weight": 0
}
]
}
})
}
const depEntList = [{
"apiVersion": "apps/v1beta1",
"kind": "Deployment",
"metadata": {
"name": "testapp",
},
"spec": {
"selector": {
"matchLabels": {
"app": "testapp",
}
},
"replicas": 1,
"template": {
"metadata": {
"labels": {
"app": "testapp",
}
},
"spec": {
"containers": [{
"name": "testapp",
"image": "testcr.azurecr.io/testapp",
"ports": [{
"containerPort": 80
}]
}]
}
}
}
}];
const serEntList = [{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "testservice"
},
"spec": {
"selector": {
"app": "testapp",
},
"ports": [{
"protocol": "TCP",
"port": 80,
"targetPort": 80
}]
}
}];
kubeCtl.getResource = jest.fn().mockReturnValue(JSON.parse(JSON.stringify(temp)));
//Invoke and assert
expect(blueGreenHelperSMI.validateTrafficSplitsState(kubeCtl, depEntList, serEntList)).toBeFalsy();
});
test("getSuffix() - returns BLUE_GREEN_SUFFIX if BLUE_GREEN_NEW_LABEL_VALUE is given, else emrty string", () => {
expect(blueGreenHelper.getSuffix('green')).toBe('-green');
});
test("getSuffix() - returns BLUE_GREEN_SUFFIX if BLUE_GREEN_NEW_LABEL_VALUE is given, else emrty string", () => {
expect(blueGreenHelper.getSuffix('random')).toBe('');
});
test("getServiceSpacLabel() - returns empty string if BLUE_GREEN_VERSION_LABEL in spec selector doesn't exist", () => {
let input = {
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "sample-deployment"
},
"spec": {
"selector": {
"matchLabels": {
"app": "sample",
"k8s.deploy.color": "green"
}
},
"template": {
"metadata": {
"labels": {
"app": "sample"
},
"annotations": {
"prometheus.io/scrape": "true",
"prometheus.io/port": "8888"
}
},
"spec": {
"containers": [{
"name": "sample",
"image": "tsugunt/sample:v34",
"ports": [{
"containerPort": 8888
}]
}]
}
}
}
}
expect(blueGreenHelperService.getServiceSpecLabel(input)).toBe('');
});
test("getDeploymentMatchLabels() - return false is input doesnt have matchLabels", () => {
let input = {
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "sample-service"
},
"spec": {
"selector": {
"app": "sample",
"k8s.deploy.color": "green"
},
"ports": [{
"protocol": "TCP",
"port": 80,
"targetPort": 8888,
"nodePort": 31002
}],
"type": "NodePort"
}
}
expect(blueGreenHelper.getDeploymentMatchLabels(input)).toBeFalsy();
});
test("getServiceSelector() - return false if spec selector does not exist", () => {
let input = {
"apiVersion": "networking.k8s.io/v1beta1",
"kind": "Ingress",
"metadata": {
"name": "test-ingress",
"annotations": {
"nginx.ingress.kubernetes.io/rewrite-target": "/"
}
},
"spec": {
"rules": [{
"http": {
"paths": [{
"path": "/testpath",
"pathType": "Prefix",
"backend": {
"serviceName": "test",
"servicePort": 80
}
}]
}
}]
}
}
expect(blueGreenHelper.getServiceSelector(input)).toBeFalsy();
});

Просмотреть файл

@ -0,0 +1,32 @@
apiVersion : apps/v1beta1
kind: Deployment
metadata:
name: testapp
spec:
selector:
matchLabels:
app: testapp
replicas: 1
template:
metadata:
labels:
app: testapp
spec:
containers:
- name: testapp
image: testcr.azurecr.io/testapp
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: testservice
spec:
selector:
app: testapp
ports:
- protocol: TCP
port: 80
targetPort: 80
---

Просмотреть файл

@ -0,0 +1,85 @@
apiVersion : apps/v1beta1
kind: Deployment
metadata:
name: testapp
spec:
selector:
matchLabels:
app: testapp
replicas: 1
template:
metadata:
labels:
app: testapp
spec:
containers:
- name: testapp
image: testcr.azurecr.io/testapp
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: testservice
spec:
selector:
app: testapp
ports:
- protocol: TCP
port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: testingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
serviceName: testservice
servicePort: 80
---
apiVersion: v1
kind: ConfigMap
metadata:
name: testconfigmap
data:
# property-like keys; each key maps to a simple value
whats_this: "testing"
why_this: "testing"
---
apiVersion: v1
kind: Service
metadata:
name: testservice-2
spec:
selector:
app: testapp-2
ports:
- protocol: TCP
port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: testingress-1
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
serviceName: testnotservice
servicePort: 80
---

Просмотреть файл

@ -19,9 +19,17 @@ inputs:
description: 'Version of kubectl. Installs a specific version of kubectl binary'
required: false
strategy:
description: 'Deployment strategy to be used. Allowed values are none, canary'
description: 'Deployment strategy to be used. Allowed values are none, canary and blue-green'
required: false
default: 'none'
route-method:
description: 'Route based on service, ingress or SMI for blue-green strategy'
required: false
default: 'service'
version-switch-buffer:
description: 'Indicates the buffer time in minutes before the switch is made to the green version (max is 300 min ie. 5hrs)'
required: false
default: 0
traffic-split-method:
description: "Traffic split method to be used. Allowed values are pod, smi"
required: false

Просмотреть файл

@ -1,9 +1,10 @@
'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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
@ -15,14 +16,34 @@ const canaryDeploymentHelper = require("../utilities/strategy-helpers/canary-dep
const SMICanaryDeploymentHelper = require("../utilities/strategy-helpers/smi-canary-deployment-helper");
const utils = require("../utilities/manifest-utilities");
const TaskInputParameters = require("../input-parameters");
const manifest_utilities_1 = require("../utilities/manifest-utilities");
const KubernetesObjectUtility = require("../utilities/resource-object-utility");
const models = require("../constants");
const KubernetesManifestUtility = require("../utilities/manifest-stability-utility");
const blue_green_helper_1 = require("../utilities/strategy-helpers/blue-green-helper");
const blue_green_helper_2 = require("../utilities/strategy-helpers/blue-green-helper");
const service_blue_green_helper_1 = require("../utilities/strategy-helpers/service-blue-green-helper");
const ingress_blue_green_helper_1 = require("../utilities/strategy-helpers/ingress-blue-green-helper");
const smi_blue_green_helper_1 = require("../utilities/strategy-helpers/smi-blue-green-helper");
const kubectl_object_model_1 = require("../kubectl-object-model");
function promote(ignoreSslErrors) {
function promote() {
return __awaiter(this, void 0, void 0, function* () {
const kubectl = new kubectl_object_model_1.Kubectl(yield utils.getKubectl(), TaskInputParameters.namespace, ignoreSslErrors);
if (!canaryDeploymentHelper.isCanaryDeploymentStrategy()) {
core.debug('Strategy is not canary deployment. Invalid request.');
const kubectl = new kubectl_object_model_1.Kubectl(yield utils.getKubectl(), TaskInputParameters.namespace, true);
if (canaryDeploymentHelper.isCanaryDeploymentStrategy()) {
yield promoteCanary(kubectl);
}
else if (blue_green_helper_2.isBlueGreenDeploymentStrategy()) {
yield promoteBlueGreen(kubectl);
}
else {
core.debug('Strategy is not canary or blue-green deployment. Invalid request.');
throw ('InvalidPromotetActionDeploymentStrategy');
}
});
}
exports.promote = promote;
function promoteCanary(kubectl) {
return __awaiter(this, void 0, void 0, function* () {
let includeServices = false;
if (canaryDeploymentHelper.isSMICanaryStrategy()) {
includeServices = true;
@ -48,4 +69,39 @@ function promote(ignoreSslErrors) {
}
});
}
exports.promote = promote;
function promoteBlueGreen(kubectl) {
return __awaiter(this, void 0, void 0, function* () {
// updated container images and pull secrets
let inputManifestFiles = manifest_utilities_1.getUpdatedManifestFiles(TaskInputParameters.manifests);
const manifestObjects = blue_green_helper_1.getManifestObjects(inputManifestFiles);
core.debug('deleting old deployment and making new ones');
let result;
if (blue_green_helper_2.isIngressRoute()) {
result = yield ingress_blue_green_helper_1.promoteBlueGreenIngress(kubectl, manifestObjects);
}
else if (blue_green_helper_2.isSMIRoute()) {
result = yield smi_blue_green_helper_1.promoteBlueGreenSMI(kubectl, manifestObjects);
}
else {
result = yield service_blue_green_helper_1.promoteBlueGreenService(kubectl, manifestObjects);
}
// checking stability of newly created deployments
const deployedManifestFiles = result.newFilePaths;
const resources = KubernetesObjectUtility.getResources(deployedManifestFiles, models.deploymentTypes.concat([models.DiscoveryAndLoadBalancerResource.service]));
yield KubernetesManifestUtility.checkManifestStability(kubectl, resources);
core.debug('routing to new deployments');
if (blue_green_helper_2.isIngressRoute()) {
ingress_blue_green_helper_1.routeBlueGreenIngress(kubectl, null, manifestObjects.serviceNameMap, manifestObjects.serviceEntityList, manifestObjects.ingressEntityList);
blue_green_helper_1.deleteWorkloadsAndServicesWithLabel(kubectl, blue_green_helper_2.GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
}
else if (blue_green_helper_2.isSMIRoute()) {
smi_blue_green_helper_1.routeBlueGreenSMI(kubectl, blue_green_helper_2.NONE_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
blue_green_helper_1.deleteWorkloadsWithLabel(kubectl, blue_green_helper_2.GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList);
smi_blue_green_helper_1.cleanupSMI(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
}
else {
service_blue_green_helper_1.routeBlueGreenService(kubectl, blue_green_helper_2.NONE_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
blue_green_helper_1.deleteWorkloadsWithLabel(kubectl, blue_green_helper_2.GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList);
}
});
}

Просмотреть файл

@ -1,9 +1,10 @@
'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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
@ -15,13 +16,29 @@ const SMICanaryDeploymentHelper = require("../utilities/strategy-helpers/smi-can
const kubectl_object_model_1 = require("../kubectl-object-model");
const utils = require("../utilities/manifest-utilities");
const TaskInputParameters = require("../input-parameters");
function reject(ignoreSslErrors) {
const service_blue_green_helper_1 = require("../utilities/strategy-helpers/service-blue-green-helper");
const ingress_blue_green_helper_1 = require("../utilities/strategy-helpers/ingress-blue-green-helper");
const smi_blue_green_helper_1 = require("../utilities/strategy-helpers/smi-blue-green-helper");
const blue_green_helper_1 = require("../utilities/strategy-helpers/blue-green-helper");
const deployment_helper_1 = require("../utilities/strategy-helpers/deployment-helper");
function reject() {
return __awaiter(this, void 0, void 0, function* () {
const kubectl = new kubectl_object_model_1.Kubectl(yield utils.getKubectl(), TaskInputParameters.namespace, ignoreSslErrors);
if (!canaryDeploymentHelper.isCanaryDeploymentStrategy()) {
core.debug('Strategy is not canary deployment. Invalid request.');
throw ('InvalidRejectActionDeploymentStrategy');
const kubectl = new kubectl_object_model_1.Kubectl(yield utils.getKubectl(), TaskInputParameters.namespace, true);
if (canaryDeploymentHelper.isCanaryDeploymentStrategy()) {
yield rejectCanary(kubectl);
}
else if (blue_green_helper_1.isBlueGreenDeploymentStrategy()) {
yield rejectBlueGreen(kubectl);
}
else {
core.debug('Strategy is not canary or blue-green deployment. Invalid request.');
throw ('InvalidDeletetActionDeploymentStrategy');
}
});
}
exports.reject = reject;
function rejectCanary(kubectl) {
return __awaiter(this, void 0, void 0, function* () {
let includeServices = false;
if (canaryDeploymentHelper.isSMICanaryStrategy()) {
core.debug('Reject deployment with SMI canary strategy');
@ -32,4 +49,17 @@ function reject(ignoreSslErrors) {
canaryDeploymentHelper.deleteCanaryDeployment(kubectl, TaskInputParameters.manifests, includeServices);
});
}
exports.reject = reject;
function rejectBlueGreen(kubectl) {
return __awaiter(this, void 0, void 0, function* () {
let inputManifestFiles = deployment_helper_1.getManifestFiles(TaskInputParameters.manifests);
if (blue_green_helper_1.isIngressRoute()) {
yield ingress_blue_green_helper_1.rejectBlueGreenIngress(kubectl, inputManifestFiles);
}
else if (blue_green_helper_1.isSMIRoute()) {
yield smi_blue_green_helper_1.rejectBlueGreenSMI(kubectl, inputManifestFiles);
}
else {
yield service_blue_green_helper_1.rejectBlueGreenService(kubectl, inputManifestFiles);
}
});
}

Просмотреть файл

@ -1,6 +1,6 @@
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.forceDeployment = exports.args = exports.baselineAndCanaryReplicas = exports.trafficSplitMethod = exports.deploymentStrategy = exports.canaryPercentage = exports.manifests = exports.imagePullSecrets = exports.containers = exports.namespace = void 0;
exports.forceDeployment = exports.args = exports.baselineAndCanaryReplicas = exports.versionSwitchBuffer = exports.routeMethod = 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');
@ -9,6 +9,8 @@ exports.manifests = core.getInput('manifests').split('\n');
exports.canaryPercentage = core.getInput('percentage');
exports.deploymentStrategy = core.getInput('strategy');
exports.trafficSplitMethod = core.getInput('traffic-split-method');
exports.routeMethod = core.getInput('route-method');
exports.versionSwitchBuffer = core.getInput('version-switch-buffer');
exports.baselineAndCanaryReplicas = core.getInput('baseline-and-canary-replicas');
exports.args = core.getInput('arguments');
exports.forceDeployment = core.getInput('force').toLowerCase() == 'true';
@ -38,3 +40,14 @@ catch (ex) {
core.setFailed("Enter a valid 'baseline-and-canary-replicas' integer value");
process.exit(1);
}
try {
const pe = parseInt(exports.versionSwitchBuffer);
if (pe < 0 || pe > 300) {
core.setFailed('Invalid buffer time, valid version-switch-buffer is a value more than or equal to 0 and lesser than or equal 300');
process.exit(1);
}
}
catch (ex) {
core.setFailed("Enter a valid 'version-switch-buffer' integer value");
process.exit(1);
}

Просмотреть файл

@ -77,10 +77,10 @@ function run() {
yield deployment_helper_1.deploy(new kubectl_object_model_1.Kubectl(kubectlPath, namespace), manifests, strategy);
}
else if (action === 'promote') {
yield promote_1.promote(true);
yield promote_1.promote();
}
else if (action === 'reject') {
yield reject_1.reject(true);
yield reject_1.reject();
}
else {
core.setFailed('Not a valid action. The allowed actions are deploy, promote, reject');

Просмотреть файл

@ -9,11 +9,17 @@ 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;
exports.isWorkloadEntity = exports.getUpdatedManifestFiles = exports.updateImagePullSecrets = exports.substituteImageNameInSpecFile = exports.getDeleteCmdArgs = exports.createKubectlArgs = exports.getKubectl = exports.getManifestFiles = void 0;
const core = require("@actions/core");
const fs = require("fs");
const yaml = require("js-yaml");
const path = require("path");
const kubectlutility = require("./kubectl-util");
const io = require("@actions/io");
const utility_1 = require("./utility");
const fileHelper = require("./files-helper");
const KubernetesObjectUtility = require("./resource-object-utility");
const TaskInputParameters = require("../input-parameters");
function getManifestFiles(manifestFilePaths) {
if (!manifestFilePaths) {
core.debug('file input is not present');
@ -189,21 +195,29 @@ function substituteImageNameInSpecContent(currentString, imageName, imageNameWit
return acc + line + '\n';
}, '');
}
function updateContainerImagesInManifestFiles(contents, containers) {
function updateContainerImagesInManifestFiles(filePaths, containers) {
if (!!containers && containers.length > 0) {
const newFilePaths = [];
const tempDirectory = fileHelper.getTempDirectory();
filePaths.forEach((filePath) => {
let contents = fs.readFileSync(filePath).toString();
containers.forEach((container) => {
let imageName = container.split(':')[0];
if (imageName.indexOf('@') > 0) {
imageName = imageName.split('@')[0];
}
if (contents.indexOf(imageName) > 0) {
contents = substituteImageNameInSpecContent(contents, imageName, container);
contents = substituteImageNameInSpecFile(contents, imageName, container);
}
});
const fileName = path.join(tempDirectory, path.basename(filePath));
fs.writeFileSync(path.join(fileName), contents);
newFilePaths.push(fileName);
});
return newFilePaths;
}
return contents;
return filePaths;
}
exports.updateContainerImagesInManifestFiles = updateContainerImagesInManifestFiles;
function updateImagePullSecrets(inputObject, newImagePullSecrets) {
if (!inputObject || !inputObject.spec || !newImagePullSecrets) {
return;
@ -223,6 +237,39 @@ function updateImagePullSecrets(inputObject, newImagePullSecrets) {
setImagePullSecrets(inputObject, existingImagePullSecretObjects);
}
exports.updateImagePullSecrets = updateImagePullSecrets;
function updateImagePullSecretsInManifestFiles(filePaths, imagePullSecrets) {
if (!!imagePullSecrets && imagePullSecrets.length > 0) {
const newObjectsList = [];
filePaths.forEach((filePath) => {
const fileContents = fs.readFileSync(filePath).toString();
yaml.safeLoadAll(fileContents, function (inputObject) {
if (!!inputObject && !!inputObject.kind) {
const kind = inputObject.kind;
if (KubernetesObjectUtility.isWorkloadEntity(kind)) {
KubernetesObjectUtility.updateImagePullSecrets(inputObject, imagePullSecrets, false);
}
newObjectsList.push(inputObject);
}
});
});
core.debug('New K8s objects after addin imagePullSecrets are :' + JSON.stringify(newObjectsList));
const newFilePaths = fileHelper.writeObjectsToFile(newObjectsList);
return newFilePaths;
}
return filePaths;
}
function getUpdatedManifestFiles(manifestFilePaths) {
let inputManifestFiles = getManifestFiles(manifestFilePaths);
if (!inputManifestFiles || inputManifestFiles.length === 0) {
throw new Error(`ManifestFileNotFound : ${manifestFilePaths}`);
}
// artifact substitution
inputManifestFiles = updateContainerImagesInManifestFiles(inputManifestFiles, TaskInputParameters.containers);
// imagePullSecrets addition
inputManifestFiles = updateImagePullSecretsInManifestFiles(inputManifestFiles, TaskInputParameters.imagePullSecrets);
return inputManifestFiles;
}
exports.getUpdatedManifestFiles = getUpdatedManifestFiles;
const workloadTypes = ['deployment', 'replicaset', 'daemonset', 'pod', 'statefulset', 'job', 'cronjob'];
function isWorkloadEntity(kind) {
if (!kind) {

Просмотреть файл

@ -1,11 +1,12 @@
'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;
exports.getResources = exports.updateSelectorLabels = exports.updateSpecLabels = exports.updateImagePullSecrets = 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");
const constants_1 = require("../constants");
const string_comparison_1 = require("./string-comparison");
const INGRESS = "Ingress";
function isDeploymentEntity(kind) {
if (!kind) {
throw ('ResourceKindNotDefined');
@ -31,6 +32,13 @@ function isServiceEntity(kind) {
return string_comparison_1.isEqual("Service", kind, string_comparison_1.StringComparer.OrdinalIgnoreCase);
}
exports.isServiceEntity = isServiceEntity;
function isIngressEntity(kind) {
if (!kind) {
throw ('ResourceKindNotDefined');
}
return string_comparison_1.isEqual(INGRESS, kind, string_comparison_1.StringComparer.OrdinalIgnoreCase);
}
exports.isIngressEntity = isIngressEntity;
function getReplicaCount(inputObject) {
if (!inputObject) {
throw ('NullInputObject');

Просмотреть файл

@ -0,0 +1,330 @@
'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.fetchResource = exports.isServiceSelectorSubsetOfMatchLabel = exports.getServiceSelector = exports.getDeploymentMatchLabels = exports.getSpecLabel = exports.getBlueGreenResourceName = exports.addBlueGreenLabelsAndAnnotations = exports.getNewBlueGreenObject = exports.createWorkloadsWithLabel = exports.isServiceRouted = exports.getManifestObjects = exports.getSuffix = exports.deleteObjects = exports.deleteWorkloadsAndServicesWithLabel = exports.cleanUp = exports.deleteWorkloadsWithLabel = exports.routeBlueGreen = exports.isSMIRoute = exports.isIngressRoute = exports.isBlueGreenDeploymentStrategy = exports.STABLE_SUFFIX = exports.GREEN_SUFFIX = exports.BLUE_GREEN_VERSION_LABEL = exports.NONE_LABEL_VALUE = exports.GREEN_LABEL_VALUE = exports.BLUE_GREEN_DEPLOYMENT_STRATEGY = void 0;
const core = require("@actions/core");
const fs = require("fs");
const yaml = require("js-yaml");
const utility_1 = require("../utility");
const constants_1 = require("../../constants");
const fileHelper = require("../files-helper");
const helper = require("../resource-object-utility");
const TaskInputParameters = require("../../input-parameters");
const service_blue_green_helper_1 = require("./service-blue-green-helper");
const ingress_blue_green_helper_1 = require("./ingress-blue-green-helper");
const smi_blue_green_helper_1 = require("./smi-blue-green-helper");
exports.BLUE_GREEN_DEPLOYMENT_STRATEGY = 'BLUE-GREEN';
exports.GREEN_LABEL_VALUE = 'green';
exports.NONE_LABEL_VALUE = 'None';
exports.BLUE_GREEN_VERSION_LABEL = 'k8s.deploy.color';
exports.GREEN_SUFFIX = '-green';
exports.STABLE_SUFFIX = '-stable';
const INGRESS_ROUTE = 'INGRESS';
const SMI_ROUTE = 'SMI';
function isBlueGreenDeploymentStrategy() {
const deploymentStrategy = TaskInputParameters.deploymentStrategy;
return deploymentStrategy && deploymentStrategy.toUpperCase() === exports.BLUE_GREEN_DEPLOYMENT_STRATEGY;
}
exports.isBlueGreenDeploymentStrategy = isBlueGreenDeploymentStrategy;
function isIngressRoute() {
const routeMethod = TaskInputParameters.routeMethod;
return routeMethod && routeMethod.toUpperCase() === INGRESS_ROUTE;
}
exports.isIngressRoute = isIngressRoute;
function isSMIRoute() {
const routeMethod = TaskInputParameters.routeMethod;
return routeMethod && routeMethod.toUpperCase() === SMI_ROUTE;
}
exports.isSMIRoute = isSMIRoute;
function routeBlueGreen(kubectl, inputManifestFiles) {
return __awaiter(this, void 0, void 0, function* () {
// get buffer time
let bufferTime = parseInt(TaskInputParameters.versionSwitchBuffer);
//logging start of buffer time
let dateNow = new Date();
console.log('starting buffer time of ' + bufferTime + ' minute/s at ' + dateNow.toISOString() + ' UTC');
// waiting
yield utility_1.sleep(bufferTime * 1000 * 60);
// logging end of buffer time
dateNow = new Date();
console.log('stopping buffer time of ' + bufferTime + ' minute/s at ' + dateNow.toISOString() + ' UTC');
const manifestObjects = getManifestObjects(inputManifestFiles);
// routing to new deployments
if (isIngressRoute()) {
ingress_blue_green_helper_1.routeBlueGreenIngress(kubectl, exports.GREEN_LABEL_VALUE, manifestObjects.serviceNameMap, manifestObjects.serviceEntityList, manifestObjects.ingressEntityList);
}
else if (isSMIRoute()) {
smi_blue_green_helper_1.routeBlueGreenSMI(kubectl, exports.GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
}
else {
service_blue_green_helper_1.routeBlueGreenService(kubectl, exports.GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
}
});
}
exports.routeBlueGreen = routeBlueGreen;
function deleteWorkloadsWithLabel(kubectl, deleteLabel, deploymentEntityList) {
let resourcesToDelete = [];
deploymentEntityList.forEach((inputObject) => {
const name = inputObject.metadata.name;
const kind = inputObject.kind;
if (deleteLabel === exports.NONE_LABEL_VALUE) {
// if dellabel is none, deletes stable deployments
const resourceToDelete = { name: name, kind: kind };
resourcesToDelete.push(resourceToDelete);
}
else {
// if dellabel is not none, then deletes new green deployments
const resourceToDelete = { name: getBlueGreenResourceName(name, exports.GREEN_SUFFIX), kind: kind };
resourcesToDelete.push(resourceToDelete);
}
});
// deletes the deployments
deleteObjects(kubectl, resourcesToDelete);
}
exports.deleteWorkloadsWithLabel = deleteWorkloadsWithLabel;
function cleanUp(kubectl, deploymentEntityList, serviceEntityList) {
// checks if services has some stable deployments to target or deletes them too
let deleteList = [];
deploymentEntityList.forEach((deploymentObject) => {
const existingDeploy = fetchResource(kubectl, deploymentObject.kind, deploymentObject.metadata.name);
if (!existingDeploy) {
serviceEntityList.forEach((serviceObject) => {
const serviceSelector = getServiceSelector(serviceObject);
const matchLabels = getDeploymentMatchLabels(deploymentObject);
if (!!serviceSelector && !!matchLabels && isServiceSelectorSubsetOfMatchLabel(serviceSelector, matchLabels)) {
const resourceToDelete = { name: serviceObject.metadata.name, kind: serviceObject.kind };
deleteList.push(resourceToDelete);
}
});
}
});
// delete service not targeting a deployment
deleteObjects(kubectl, deleteList);
}
exports.cleanUp = cleanUp;
function deleteWorkloadsAndServicesWithLabel(kubectl, deleteLabel, deploymentEntityList, serviceEntityList) {
// need to delete services and deployments
const deletionEntitiesList = deploymentEntityList.concat(serviceEntityList);
let resourcesToDelete = [];
deletionEntitiesList.forEach((inputObject) => {
const name = inputObject.metadata.name;
const kind = inputObject.kind;
if (deleteLabel === exports.NONE_LABEL_VALUE) {
// if not dellabel, delete stable objects
const resourceToDelete = { name: name, kind: kind };
resourcesToDelete.push(resourceToDelete);
}
else {
// else delete green labels
const resourceToDelete = { name: getBlueGreenResourceName(name, exports.GREEN_SUFFIX), kind: kind };
resourcesToDelete.push(resourceToDelete);
}
});
deleteObjects(kubectl, resourcesToDelete);
}
exports.deleteWorkloadsAndServicesWithLabel = deleteWorkloadsAndServicesWithLabel;
function deleteObjects(kubectl, deleteList) {
// delete services and deployments
deleteList.forEach((delObject) => {
try {
const result = kubectl.delete([delObject.kind, delObject.name]);
utility_1.checkForErrors([result]);
}
catch (ex) {
// Ignore failures of delete if doesn't exist
}
});
}
exports.deleteObjects = deleteObjects;
function getSuffix(label) {
if (label === exports.GREEN_LABEL_VALUE) {
return exports.GREEN_SUFFIX;
}
else {
return '';
}
}
exports.getSuffix = getSuffix;
// other common functions
function getManifestObjects(filePaths) {
const deploymentEntityList = [];
const serviceEntityList = [];
const ingressEntityList = [];
const otherEntitiesList = [];
filePaths.forEach((filePath) => {
const fileContents = fs.readFileSync(filePath);
yaml.safeLoadAll(fileContents, function (inputObject) {
if (!!inputObject) {
const kind = inputObject.kind;
if (helper.isDeploymentEntity(kind)) {
deploymentEntityList.push(inputObject);
}
else if (helper.isServiceEntity(kind)) {
serviceEntityList.push(inputObject);
}
else if (helper.isIngressEntity(kind)) {
ingressEntityList.push(inputObject);
}
else {
otherEntitiesList.push(inputObject);
}
}
});
});
let serviceNameMap = new Map();
// find all services and add their names with blue green suffix
serviceEntityList.forEach(inputObject => {
const name = inputObject.metadata.name;
serviceNameMap.set(name, getBlueGreenResourceName(name, exports.GREEN_SUFFIX));
});
return { serviceEntityList: serviceEntityList, serviceNameMap: serviceNameMap, deploymentEntityList: deploymentEntityList, ingressEntityList: ingressEntityList, otherObjects: otherEntitiesList };
}
exports.getManifestObjects = getManifestObjects;
function isServiceRouted(serviceObject, deploymentEntityList) {
let shouldBeRouted = false;
const serviceSelector = getServiceSelector(serviceObject);
if (!!serviceSelector) {
deploymentEntityList.every((depObject) => {
// finding if there is a deployment in the given manifests the service targets
const matchLabels = getDeploymentMatchLabels(depObject);
if (!!matchLabels && isServiceSelectorSubsetOfMatchLabel(serviceSelector, matchLabels)) {
shouldBeRouted = true;
return false;
}
});
}
return shouldBeRouted;
}
exports.isServiceRouted = isServiceRouted;
function createWorkloadsWithLabel(kubectl, deploymentObjectList, nextLabel) {
const newObjectsList = [];
deploymentObjectList.forEach((inputObject) => {
// creating deployment with label
const newBlueGreenObject = getNewBlueGreenObject(inputObject, nextLabel);
core.debug('New blue-green object is: ' + JSON.stringify(newBlueGreenObject));
newObjectsList.push(newBlueGreenObject);
});
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
const result = kubectl.apply(manifestFiles);
return { 'result': result, 'newFilePaths': manifestFiles };
}
exports.createWorkloadsWithLabel = createWorkloadsWithLabel;
function getNewBlueGreenObject(inputObject, labelValue) {
const newObject = JSON.parse(JSON.stringify(inputObject));
// Updating name only if label is green label is given
if (labelValue === exports.GREEN_LABEL_VALUE) {
newObject.metadata.name = getBlueGreenResourceName(inputObject.metadata.name, exports.GREEN_SUFFIX);
}
// Adding labels and annotations
addBlueGreenLabelsAndAnnotations(newObject, labelValue);
return newObject;
}
exports.getNewBlueGreenObject = getNewBlueGreenObject;
function addBlueGreenLabelsAndAnnotations(inputObject, labelValue) {
//creating the k8s.deploy.color label
const newLabels = new Map();
newLabels[exports.BLUE_GREEN_VERSION_LABEL] = labelValue;
// updating object labels and selector labels
helper.updateObjectLabels(inputObject, newLabels, false);
helper.updateSelectorLabels(inputObject, newLabels, false);
// updating spec labels if it is a service
if (!helper.isServiceEntity(inputObject.kind)) {
helper.updateSpecLabels(inputObject, newLabels, false);
}
}
exports.addBlueGreenLabelsAndAnnotations = addBlueGreenLabelsAndAnnotations;
function getBlueGreenResourceName(name, suffix) {
return `${name}${suffix}`;
}
exports.getBlueGreenResourceName = getBlueGreenResourceName;
function getSpecLabel(inputObject) {
if (!!inputObject && inputObject.spec && inputObject.spec.selector && inputObject.spec.selector.matchLabels && inputObject.spec.selector.matchLabels[exports.BLUE_GREEN_VERSION_LABEL]) {
return inputObject.spec.selector.matchLabels[exports.BLUE_GREEN_VERSION_LABEL];
}
return '';
}
exports.getSpecLabel = getSpecLabel;
function getDeploymentMatchLabels(deploymentObject) {
if (!!deploymentObject && deploymentObject.kind.toUpperCase() == constants_1.KubernetesWorkload.pod.toUpperCase() && !!deploymentObject.metadata && !!deploymentObject.metadata.labels) {
return JSON.stringify(deploymentObject.metadata.labels);
}
else if (!!deploymentObject && deploymentObject.spec && deploymentObject.spec.selector && deploymentObject.spec.selector.matchLabels) {
return JSON.stringify(deploymentObject.spec.selector.matchLabels);
}
return '';
}
exports.getDeploymentMatchLabels = getDeploymentMatchLabels;
function getServiceSelector(serviceObject) {
if (!!serviceObject && serviceObject.spec && serviceObject.spec.selector) {
return JSON.stringify(serviceObject.spec.selector);
}
else
return '';
}
exports.getServiceSelector = getServiceSelector;
function isServiceSelectorSubsetOfMatchLabel(serviceSelector, matchLabels) {
let serviceSelectorMap = new Map();
let matchLabelsMap = new Map();
JSON.parse(serviceSelector, (key, value) => {
serviceSelectorMap.set(key, value);
});
JSON.parse(matchLabels, (key, value) => {
matchLabelsMap.set(key, value);
});
let isMatch = true;
serviceSelectorMap.forEach((value, key) => {
if (!!key && (!matchLabelsMap.has(key) || matchLabelsMap.get(key)) != value) {
isMatch = false;
}
});
return isMatch;
}
exports.isServiceSelectorSubsetOfMatchLabel = isServiceSelectorSubsetOfMatchLabel;
function fetchResource(kubectl, kind, name) {
const result = kubectl.getResource(kind, name);
if (result == null || !!result.stderr) {
return null;
}
if (!!result.stdout) {
const resource = JSON.parse(result.stdout);
try {
UnsetsClusterSpecficDetails(resource);
return resource;
}
catch (ex) {
core.debug('Exception occurred while Parsing ' + resource + ' in Json object');
core.debug(`Exception:${ex}`);
}
}
return null;
}
exports.fetchResource = fetchResource;
function UnsetsClusterSpecficDetails(resource) {
if (resource == null) {
return;
}
// Unsets the cluster specific details in the object
if (!!resource) {
const metadata = resource.metadata;
const status = resource.status;
if (!!metadata) {
const newMetadata = {
'annotations': metadata.annotations,
'labels': metadata.labels,
'name': metadata.name
};
resource.metadata = newMetadata;
}
if (!!status) {
resource.status = {};
}
}
}

Просмотреть файл

@ -1,17 +1,16 @@
'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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
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.deploy = void 0;
exports.getManifestFiles = exports.deploy = void 0;
const fs = require("fs");
const path = require("path");
const core = require("@actions/core");
const yaml = require("js-yaml");
const canaryDeploymentHelper = require("./canary-deployment-helper");
const KubernetesObjectUtility = require("../resource-object-utility");
@ -21,22 +20,27 @@ const fileHelper = require("../files-helper");
const utils = require("../manifest-utilities");
const KubernetesManifestUtility = require("../manifest-stability-utility");
const KubernetesConstants = require("../../constants");
const manifest_utilities_1 = require("../manifest-utilities");
const pod_canary_deployment_helper_1 = require("./pod-canary-deployment-helper");
const smi_canary_deployment_helper_1 = require("./smi-canary-deployment-helper");
const utility_1 = require("../utility");
const blue_green_helper_1 = require("./blue-green-helper");
const service_blue_green_helper_1 = require("./service-blue-green-helper");
const ingress_blue_green_helper_1 = require("./ingress-blue-green-helper");
const smi_blue_green_helper_1 = require("./smi-blue-green-helper");
function deploy(kubectl, manifestFilePaths, deploymentStrategy) {
return __awaiter(this, void 0, void 0, function* () {
// get manifest files
let inputManifestFiles = getManifestFiles(manifestFilePaths);
// artifact substitution
inputManifestFiles = updateContainerImagesInManifestFiles(inputManifestFiles, TaskInputParameters.containers);
// imagePullSecrets addition
inputManifestFiles = updateImagePullSecretsInManifestFiles(inputManifestFiles, TaskInputParameters.imagePullSecrets);
let inputManifestFiles = manifest_utilities_1.getUpdatedManifestFiles(manifestFilePaths);
// deployment
const deployedManifestFiles = deployManifests(inputManifestFiles, kubectl, isCanaryDeploymentStrategy(deploymentStrategy));
const deployedManifestFiles = deployManifests(inputManifestFiles, kubectl, isCanaryDeploymentStrategy(deploymentStrategy), blue_green_helper_1.isBlueGreenDeploymentStrategy());
// check manifest stability
const resourceTypes = KubernetesObjectUtility.getResources(deployedManifestFiles, models.deploymentTypes.concat([KubernetesConstants.DiscoveryAndLoadBalancerResource.service]));
yield checkManifestStability(kubectl, resourceTypes);
// route blue-green deployments
if (blue_green_helper_1.isBlueGreenDeploymentStrategy()) {
yield blue_green_helper_1.routeBlueGreen(kubectl, inputManifestFiles);
}
// print ingress resources
const ingressResources = KubernetesObjectUtility.getResources(deployedManifestFiles, [KubernetesConstants.DiscoveryAndLoadBalancerResource.ingress]);
ingressResources.forEach(ingressResource => {
@ -52,7 +56,8 @@ function getManifestFiles(manifestFilePaths) {
}
return files;
}
function deployManifests(files, kubectl, isCanaryDeploymentStrategy) {
exports.getManifestFiles = getManifestFiles;
function deployManifests(files, kubectl, isCanaryDeploymentStrategy, isBlueGreenDeploymentStrategy) {
let result;
if (isCanaryDeploymentStrategy) {
let canaryDeploymentOutput;
@ -65,6 +70,20 @@ function deployManifests(files, kubectl, isCanaryDeploymentStrategy) {
result = canaryDeploymentOutput.result;
files = canaryDeploymentOutput.newFilePaths;
}
else if (isBlueGreenDeploymentStrategy) {
let blueGreenDeploymentOutput;
if (blue_green_helper_1.isIngressRoute()) {
blueGreenDeploymentOutput = ingress_blue_green_helper_1.deployBlueGreenIngress(kubectl, files);
}
else if (blue_green_helper_1.isSMIRoute()) {
blueGreenDeploymentOutput = smi_blue_green_helper_1.deployBlueGreenSMI(kubectl, files);
}
else {
blueGreenDeploymentOutput = service_blue_green_helper_1.deployBlueGreenService(kubectl, files);
}
result = blueGreenDeploymentOutput.result;
files = blueGreenDeploymentOutput.newFilePaths;
}
else {
if (canaryDeploymentHelper.isSMICanaryStrategy()) {
const updatedManifests = appendStableVersionLabelToResource(files, kubectl);
@ -102,50 +121,6 @@ function checkManifestStability(kubectl, resources) {
yield KubernetesManifestUtility.checkManifestStability(kubectl, resources);
});
}
function updateContainerImagesInManifestFiles(filePaths, containers) {
if (!!containers && containers.length > 0) {
const newFilePaths = [];
const tempDirectory = fileHelper.getTempDirectory();
filePaths.forEach((filePath) => {
let contents = fs.readFileSync(filePath).toString();
containers.forEach((container) => {
let imageName = container.split(':')[0];
if (imageName.indexOf('@') > 0) {
imageName = imageName.split('@')[0];
}
if (contents.indexOf(imageName) > 0) {
contents = utils.substituteImageNameInSpecFile(contents, imageName, container);
}
});
const fileName = path.join(tempDirectory, path.basename(filePath));
fs.writeFileSync(path.join(fileName), contents);
newFilePaths.push(fileName);
});
return newFilePaths;
}
return filePaths;
}
function updateImagePullSecretsInManifestFiles(filePaths, imagePullSecrets) {
if (!!imagePullSecrets && imagePullSecrets.length > 0) {
const newObjectsList = [];
filePaths.forEach((filePath) => {
const fileContents = fs.readFileSync(filePath).toString();
yaml.safeLoadAll(fileContents, function (inputObject) {
if (!!inputObject && !!inputObject.kind) {
const kind = inputObject.kind;
if (KubernetesObjectUtility.isWorkloadEntity(kind)) {
KubernetesObjectUtility.updateImagePullSecrets(inputObject, imagePullSecrets, false);
}
newObjectsList.push(inputObject);
}
});
});
core.debug('New K8s objects after addin imagePullSecrets are :' + JSON.stringify(newObjectsList));
const newFilePaths = fileHelper.writeObjectsToFile(newObjectsList);
return newFilePaths;
}
return filePaths;
}
function isCanaryDeploymentStrategy(deploymentStrategy) {
return deploymentStrategy != null && deploymentStrategy.toUpperCase() === canaryDeploymentHelper.CANARY_DEPLOYMENT_STRATEGY.toUpperCase();
}

Просмотреть файл

@ -0,0 +1,158 @@
'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.updateIngressBackend = exports.getUpdatedBlueGreenIngress = exports.validateIngressesState = exports.routeBlueGreenIngress = exports.rejectBlueGreenIngress = exports.promoteBlueGreenIngress = exports.deployBlueGreenIngress = void 0;
const core = require("@actions/core");
const fileHelper = require("../files-helper");
const blue_green_helper_1 = require("./blue-green-helper");
const blue_green_helper_2 = require("./blue-green-helper");
const BACKEND = 'BACKEND';
function deployBlueGreenIngress(kubectl, filePaths) {
// get all kubernetes objects defined in manifest files
const manifestObjects = blue_green_helper_1.getManifestObjects(filePaths);
// create deployments with green label value
const result = blue_green_helper_1.createWorkloadsWithLabel(kubectl, manifestObjects.deploymentEntityList, blue_green_helper_2.GREEN_LABEL_VALUE);
// create new services and other objects
let newObjectsList = [];
manifestObjects.serviceEntityList.forEach(inputObject => {
const newBlueGreenObject = blue_green_helper_1.getNewBlueGreenObject(inputObject, blue_green_helper_2.GREEN_LABEL_VALUE);
;
core.debug('New blue-green object is: ' + JSON.stringify(newBlueGreenObject));
newObjectsList.push(newBlueGreenObject);
});
newObjectsList = newObjectsList.concat(manifestObjects.otherObjects);
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
// return results to check for manifest stability
return result;
}
exports.deployBlueGreenIngress = deployBlueGreenIngress;
function promoteBlueGreenIngress(kubectl, manifestObjects) {
return __awaiter(this, void 0, void 0, function* () {
//checking if anything to promote
if (!validateIngressesState(kubectl, manifestObjects.ingressEntityList, manifestObjects.serviceNameMap)) {
throw ('NotInPromoteStateIngress');
}
// create stable deployments with new configuration
const result = blue_green_helper_1.createWorkloadsWithLabel(kubectl, manifestObjects.deploymentEntityList, blue_green_helper_2.NONE_LABEL_VALUE);
// create stable services with new configuration
const newObjectsList = [];
manifestObjects.serviceEntityList.forEach((inputObject) => {
const newBlueGreenObject = blue_green_helper_1.getNewBlueGreenObject(inputObject, blue_green_helper_2.NONE_LABEL_VALUE);
core.debug('New blue-green object is: ' + JSON.stringify(newBlueGreenObject));
newObjectsList.push(newBlueGreenObject);
});
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
// returning deployments to check for rollout stability
return result;
});
}
exports.promoteBlueGreenIngress = promoteBlueGreenIngress;
function rejectBlueGreenIngress(kubectl, filePaths) {
return __awaiter(this, void 0, void 0, function* () {
// get all kubernetes objects defined in manifest files
const manifestObjects = blue_green_helper_1.getManifestObjects(filePaths);
// routing ingress to stables services
routeBlueGreenIngress(kubectl, null, manifestObjects.serviceNameMap, manifestObjects.serviceEntityList, manifestObjects.ingressEntityList);
// deleting green services and deployments
blue_green_helper_1.deleteWorkloadsAndServicesWithLabel(kubectl, blue_green_helper_2.GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
});
}
exports.rejectBlueGreenIngress = rejectBlueGreenIngress;
function routeBlueGreenIngress(kubectl, nextLabel, serviceNameMap, serviceEntityList, ingressEntityList) {
let newObjectsList = [];
if (!nextLabel) {
newObjectsList = newObjectsList.concat(ingressEntityList);
}
else {
ingressEntityList.forEach((inputObject) => {
if (isIngressRouted(inputObject, serviceNameMap)) {
const newBlueGreenIngressObject = getUpdatedBlueGreenIngress(inputObject, serviceNameMap, blue_green_helper_2.GREEN_LABEL_VALUE);
newObjectsList.push(newBlueGreenIngressObject);
}
else {
newObjectsList.push(inputObject);
}
});
}
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
}
exports.routeBlueGreenIngress = routeBlueGreenIngress;
function validateIngressesState(kubectl, ingressEntityList, serviceNameMap) {
let areIngressesTargetingNewServices = true;
ingressEntityList.forEach((inputObject) => {
if (isIngressRouted(inputObject, serviceNameMap)) {
//querying existing ingress
let existingIngress = blue_green_helper_1.fetchResource(kubectl, inputObject.kind, inputObject.metadata.name);
if (!!existingIngress) {
let currentLabel;
// checking its label
try {
currentLabel = existingIngress.metadata.labels[blue_green_helper_2.BLUE_GREEN_VERSION_LABEL];
}
catch (_a) {
// if no label exists, then not an ingress targeting green deployments
areIngressesTargetingNewServices = false;
}
if (currentLabel != blue_green_helper_2.GREEN_LABEL_VALUE) {
// if not green label, then wrong configuration
areIngressesTargetingNewServices = false;
}
}
else {
// no ingress at all, so nothing to promote
areIngressesTargetingNewServices = false;
}
}
});
return areIngressesTargetingNewServices;
}
exports.validateIngressesState = validateIngressesState;
function isIngressRouted(ingressObject, serviceNameMap) {
let isIngressRouted = false;
// sees if ingress targets a service in the given manifests
JSON.parse(JSON.stringify(ingressObject), (key, value) => {
if (key === 'serviceName' && serviceNameMap.has(value)) {
isIngressRouted = true;
}
return value;
});
return isIngressRouted;
}
function getUpdatedBlueGreenIngress(inputObject, serviceNameMap, type) {
if (!type) {
// returning original with no modifications
return inputObject;
}
const newObject = JSON.parse(JSON.stringify(inputObject));
// adding green labels and values
blue_green_helper_1.addBlueGreenLabelsAndAnnotations(newObject, type);
// Updating ingress labels
let finalObject = updateIngressBackend(newObject, serviceNameMap);
return finalObject;
}
exports.getUpdatedBlueGreenIngress = getUpdatedBlueGreenIngress;
function updateIngressBackend(inputObject, serviceNameMap) {
inputObject = JSON.parse(JSON.stringify(inputObject), (key, value) => {
if (key.toUpperCase() === BACKEND) {
let serviceName = value.serviceName;
if (serviceNameMap.has(serviceName)) {
// updating service name with corresponding bluegreen name only if service is provied in given manifests
value.serviceName = serviceNameMap.get(serviceName);
}
}
return value;
});
return inputObject;
}
exports.updateIngressBackend = updateIngressBackend;

Просмотреть файл

@ -0,0 +1,108 @@
'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.getServiceSpecLabel = exports.validateServicesState = exports.routeBlueGreenService = exports.rejectBlueGreenService = exports.promoteBlueGreenService = exports.deployBlueGreenService = void 0;
const fileHelper = require("../files-helper");
const blue_green_helper_1 = require("./blue-green-helper");
const blue_green_helper_2 = require("./blue-green-helper");
function deployBlueGreenService(kubectl, filePaths) {
// get all kubernetes objects defined in manifest files
const manifestObjects = blue_green_helper_1.getManifestObjects(filePaths);
// create deployments with green label value
const result = blue_green_helper_1.createWorkloadsWithLabel(kubectl, manifestObjects.deploymentEntityList, blue_green_helper_2.GREEN_LABEL_VALUE);
// create other non deployment and non service entities
const newObjectsList = manifestObjects.otherObjects.concat(manifestObjects.ingressEntityList);
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
// returning deployment details to check for rollout stability
return result;
}
exports.deployBlueGreenService = deployBlueGreenService;
function promoteBlueGreenService(kubectl, manifestObjects) {
return __awaiter(this, void 0, void 0, function* () {
// checking if services are in the right state ie. targeting green deployments
if (!validateServicesState(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList)) {
throw ('NotInPromoteState');
}
// creating stable deployments with new configurations
const result = blue_green_helper_1.createWorkloadsWithLabel(kubectl, manifestObjects.deploymentEntityList, blue_green_helper_2.NONE_LABEL_VALUE);
// returning deployment details to check for rollout stability
return result;
});
}
exports.promoteBlueGreenService = promoteBlueGreenService;
function rejectBlueGreenService(kubectl, filePaths) {
return __awaiter(this, void 0, void 0, function* () {
// get all kubernetes objects defined in manifest files
const manifestObjects = blue_green_helper_1.getManifestObjects(filePaths);
// routing to stable objects
routeBlueGreenService(kubectl, blue_green_helper_2.NONE_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
// seeing if we should even delete the service
blue_green_helper_1.cleanUp(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
// deleting the new deployments with green suffix
blue_green_helper_1.deleteWorkloadsWithLabel(kubectl, blue_green_helper_2.GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList);
});
}
exports.rejectBlueGreenService = rejectBlueGreenService;
function routeBlueGreenService(kubectl, nextLabel, deploymentEntityList, serviceEntityList) {
const newObjectsList = [];
serviceEntityList.forEach((serviceObject) => {
if (blue_green_helper_1.isServiceRouted(serviceObject, deploymentEntityList)) {
// if service is routed, point it to given label
const newBlueGreenServiceObject = getUpdatedBlueGreenService(serviceObject, nextLabel);
newObjectsList.push(newBlueGreenServiceObject);
}
else {
// if service is not routed, just push the original service
newObjectsList.push(serviceObject);
}
});
// configures the services
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
}
exports.routeBlueGreenService = routeBlueGreenService;
// adding green labels to configure existing service
function getUpdatedBlueGreenService(inputObject, labelValue) {
const newObject = JSON.parse(JSON.stringify(inputObject));
// Adding labels and annotations.
blue_green_helper_1.addBlueGreenLabelsAndAnnotations(newObject, labelValue);
return newObject;
}
function validateServicesState(kubectl, deploymentEntityList, serviceEntityList) {
let areServicesGreen = true;
serviceEntityList.forEach((serviceObject) => {
if (blue_green_helper_1.isServiceRouted(serviceObject, deploymentEntityList)) {
// finding the existing routed service
const existingService = blue_green_helper_1.fetchResource(kubectl, serviceObject.kind, serviceObject.metadata.name);
if (!!existingService) {
let currentLabel = getServiceSpecLabel(existingService);
if (currentLabel != blue_green_helper_2.GREEN_LABEL_VALUE) {
// service should be targeting deployments with green label
areServicesGreen = false;
}
}
else {
// service targeting deployment doesn't exist
areServicesGreen = false;
}
}
});
return areServicesGreen;
}
exports.validateServicesState = validateServicesState;
function getServiceSpecLabel(inputObject) {
if (!!inputObject && inputObject.spec && inputObject.spec.selector && inputObject.spec.selector[blue_green_helper_2.BLUE_GREEN_VERSION_LABEL]) {
return inputObject.spec.selector[blue_green_helper_2.BLUE_GREEN_VERSION_LABEL];
}
return '';
}
exports.getServiceSpecLabel = getServiceSpecLabel;

Просмотреть файл

@ -0,0 +1,192 @@
'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.cleanupSMI = exports.validateTrafficSplitsState = exports.routeBlueGreenSMI = exports.getSMIServiceResource = exports.setupSMI = exports.rejectBlueGreenSMI = exports.promoteBlueGreenSMI = exports.deployBlueGreenSMI = void 0;
const kubectlUtils = require("../kubectl-util");
const fileHelper = require("../files-helper");
const blue_green_helper_1 = require("./blue-green-helper");
const blue_green_helper_2 = require("./blue-green-helper");
let trafficSplitAPIVersion = "";
const TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX = '-trafficsplit';
const TRAFFIC_SPLIT_OBJECT = 'TrafficSplit';
const MIN_VAL = '0';
const MAX_VAL = '100';
function deployBlueGreenSMI(kubectl, filePaths) {
// get all kubernetes objects defined in manifest files
const manifestObjects = blue_green_helper_1.getManifestObjects(filePaths);
// creating services and other objects
const newObjectsList = manifestObjects.otherObjects.concat(manifestObjects.serviceEntityList).concat(manifestObjects.ingressEntityList);
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
// make extraservices and trafficsplit
setupSMI(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
// create new deloyments
const result = blue_green_helper_1.createWorkloadsWithLabel(kubectl, manifestObjects.deploymentEntityList, blue_green_helper_2.GREEN_LABEL_VALUE);
// return results to check for manifest stability
return result;
}
exports.deployBlueGreenSMI = deployBlueGreenSMI;
function promoteBlueGreenSMI(kubectl, manifestObjects) {
return __awaiter(this, void 0, void 0, function* () {
// checking if there is something to promote
if (!validateTrafficSplitsState(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList)) {
throw ('NotInPromoteStateSMI');
}
// create stable deployments with new configuration
const result = blue_green_helper_1.createWorkloadsWithLabel(kubectl, manifestObjects.deploymentEntityList, blue_green_helper_2.NONE_LABEL_VALUE);
// return result to check for stability
return result;
});
}
exports.promoteBlueGreenSMI = promoteBlueGreenSMI;
function rejectBlueGreenSMI(kubectl, filePaths) {
return __awaiter(this, void 0, void 0, function* () {
// get all kubernetes objects defined in manifest files
const manifestObjects = blue_green_helper_1.getManifestObjects(filePaths);
// routing trafficsplit to stable deploymetns
routeBlueGreenSMI(kubectl, blue_green_helper_2.NONE_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
// deciding whether to delete services or not
blue_green_helper_1.cleanUp(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
// deleting rejected new bluegreen deplyments
blue_green_helper_1.deleteWorkloadsWithLabel(kubectl, blue_green_helper_2.GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList);
//deleting trafficsplit and extra services
cleanupSMI(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
});
}
exports.rejectBlueGreenSMI = rejectBlueGreenSMI;
function setupSMI(kubectl, deploymentEntityList, serviceEntityList) {
const newObjectsList = [];
const trafficObjectList = [];
serviceEntityList.forEach((serviceObject) => {
if (blue_green_helper_1.isServiceRouted(serviceObject, deploymentEntityList)) {
// create a trafficsplit for service
trafficObjectList.push(serviceObject);
// setting up the services for trafficsplit
const newStableService = getSMIServiceResource(serviceObject, blue_green_helper_2.STABLE_SUFFIX);
const newGreenService = getSMIServiceResource(serviceObject, blue_green_helper_2.GREEN_SUFFIX);
newObjectsList.push(newStableService);
newObjectsList.push(newGreenService);
}
});
// creating services
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
// route to stable service
trafficObjectList.forEach((inputObject) => {
createTrafficSplitObject(kubectl, inputObject.metadata.name, blue_green_helper_2.NONE_LABEL_VALUE);
});
}
exports.setupSMI = setupSMI;
function createTrafficSplitObject(kubectl, name, nextLabel) {
// getting smi spec api version
trafficSplitAPIVersion = kubectlUtils.getTrafficSplitAPIVersion(kubectl);
// deciding weights based on nextlabel
let stableWeight;
let greenWeight;
if (nextLabel === blue_green_helper_2.GREEN_LABEL_VALUE) {
stableWeight = parseInt(MIN_VAL);
greenWeight = parseInt(MAX_VAL);
}
else {
stableWeight = parseInt(MAX_VAL);
greenWeight = parseInt(MIN_VAL);
}
//traffic split json
const trafficSplitObject = `{
"apiVersion": "${trafficSplitAPIVersion}",
"kind": "TrafficSplit",
"metadata": {
"name": "${blue_green_helper_1.getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX)}"
},
"spec": {
"service": "${name}",
"backends": [
{
"service": "${blue_green_helper_1.getBlueGreenResourceName(name, blue_green_helper_2.STABLE_SUFFIX)}",
"weight": ${stableWeight}
},
{
"service": "${blue_green_helper_1.getBlueGreenResourceName(name, blue_green_helper_2.GREEN_SUFFIX)}",
"weight": ${greenWeight}
}
]
}
}`;
// creating trafficplit object
const trafficSplitManifestFile = fileHelper.writeManifestToFile(trafficSplitObject, TRAFFIC_SPLIT_OBJECT, blue_green_helper_1.getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX));
kubectl.apply(trafficSplitManifestFile);
}
function getSMIServiceResource(inputObject, suffix) {
const newObject = JSON.parse(JSON.stringify(inputObject));
if (suffix === blue_green_helper_2.STABLE_SUFFIX) {
// adding stable suffix to service name
newObject.metadata.name = blue_green_helper_1.getBlueGreenResourceName(inputObject.metadata.name, blue_green_helper_2.STABLE_SUFFIX);
return blue_green_helper_1.getNewBlueGreenObject(newObject, blue_green_helper_2.NONE_LABEL_VALUE);
}
else {
// green label will be added for these
return blue_green_helper_1.getNewBlueGreenObject(newObject, blue_green_helper_2.GREEN_LABEL_VALUE);
}
}
exports.getSMIServiceResource = getSMIServiceResource;
function routeBlueGreenSMI(kubectl, nextLabel, deploymentEntityList, serviceEntityList) {
serviceEntityList.forEach((serviceObject) => {
if (blue_green_helper_1.isServiceRouted(serviceObject, deploymentEntityList)) {
// routing trafficsplit to given label
createTrafficSplitObject(kubectl, serviceObject.metadata.name, nextLabel);
}
});
}
exports.routeBlueGreenSMI = routeBlueGreenSMI;
function validateTrafficSplitsState(kubectl, deploymentEntityList, serviceEntityList) {
let areTrafficSplitsInRightState = true;
serviceEntityList.forEach((serviceObject) => {
if (blue_green_helper_1.isServiceRouted(serviceObject, deploymentEntityList)) {
const name = serviceObject.metadata.name;
let trafficSplitObject = blue_green_helper_1.fetchResource(kubectl, TRAFFIC_SPLIT_OBJECT, blue_green_helper_1.getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX));
if (!trafficSplitObject) {
// no trafficplit exits
areTrafficSplitsInRightState = false;
}
trafficSplitObject = JSON.parse(JSON.stringify(trafficSplitObject));
trafficSplitObject.spec.backends.forEach(element => {
// checking if trafficsplit in right state to deploy
if (element.service === blue_green_helper_1.getBlueGreenResourceName(name, blue_green_helper_2.GREEN_SUFFIX)) {
if (element.weight != MAX_VAL) {
// green service should have max weight
areTrafficSplitsInRightState = false;
}
}
if (element.service === blue_green_helper_1.getBlueGreenResourceName(name, blue_green_helper_2.STABLE_SUFFIX)) {
if (element.weight != MIN_VAL) {
// stable service should have 0 weight
areTrafficSplitsInRightState = false;
}
}
});
}
});
return areTrafficSplitsInRightState;
}
exports.validateTrafficSplitsState = validateTrafficSplitsState;
function cleanupSMI(kubectl, deploymentEntityList, serviceEntityList) {
const deleteList = [];
serviceEntityList.forEach((serviceObject) => {
if (blue_green_helper_1.isServiceRouted(serviceObject, deploymentEntityList)) {
deleteList.push({ name: blue_green_helper_1.getBlueGreenResourceName(serviceObject.metadata.name, blue_green_helper_2.GREEN_SUFFIX), kind: serviceObject.kind });
deleteList.push({ name: blue_green_helper_1.getBlueGreenResourceName(serviceObject.metadata.name, blue_green_helper_2.STABLE_SUFFIX), kind: serviceObject.kind });
deleteList.push({ name: blue_green_helper_1.getBlueGreenResourceName(serviceObject.metadata.name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX), kind: TRAFFIC_SPLIT_OBJECT });
}
});
// deleting all objects
blue_green_helper_1.deleteObjects(kubectl, deleteList);
}
exports.cleanupSMI = cleanupSMI;

Просмотреть файл

@ -1,22 +1,35 @@
'use strict';
import * as core from '@actions/core';
import * as deploymentHelper from '../utilities/strategy-helpers/deployment-helper';
import * as canaryDeploymentHelper from '../utilities/strategy-helpers/canary-deployment-helper';
import * as SMICanaryDeploymentHelper from '../utilities/strategy-helpers/smi-canary-deployment-helper';
import * as utils from '../utilities/manifest-utilities';
import * as TaskInputParameters from '../input-parameters';
import { getUpdatedManifestFiles } from '../utilities/manifest-utilities'
import * as KubernetesObjectUtility from '../utilities/resource-object-utility';
import * as models from '../constants';
import * as KubernetesManifestUtility from '../utilities/manifest-stability-utility';
import { getManifestObjects, deleteWorkloadsWithLabel, deleteWorkloadsAndServicesWithLabel } from '../utilities/strategy-helpers/blue-green-helper';
import { isBlueGreenDeploymentStrategy, isIngressRoute, isSMIRoute, GREEN_LABEL_VALUE, NONE_LABEL_VALUE } from '../utilities/strategy-helpers/blue-green-helper';
import { routeBlueGreenService, promoteBlueGreenService } from '../utilities/strategy-helpers/service-blue-green-helper';
import { routeBlueGreenIngress, promoteBlueGreenIngress } from '../utilities/strategy-helpers/ingress-blue-green-helper';
import { routeBlueGreenSMI, promoteBlueGreenSMI, cleanupSMI } from '../utilities/strategy-helpers/smi-blue-green-helper';
import { Kubectl, Resource } from '../kubectl-object-model';
import { Kubectl } from '../kubectl-object-model';
export async function promote() {
const kubectl = new Kubectl(await utils.getKubectl(), TaskInputParameters.namespace, true);
export async function promote(ignoreSslErrors?: boolean) {
const kubectl = new Kubectl(await utils.getKubectl(), TaskInputParameters.namespace, ignoreSslErrors);
if (!canaryDeploymentHelper.isCanaryDeploymentStrategy()) {
core.debug('Strategy is not canary deployment. Invalid request.');
if (canaryDeploymentHelper.isCanaryDeploymentStrategy()) {
await promoteCanary(kubectl);
} else if (isBlueGreenDeploymentStrategy()) {
await promoteBlueGreen(kubectl);
} else {
core.debug('Strategy is not canary or blue-green deployment. Invalid request.');
throw ('InvalidPromotetActionDeploymentStrategy');
}
}
async function promoteCanary(kubectl: Kubectl) {
let includeServices = false;
if (canaryDeploymentHelper.isSMICanaryStrategy()) {
includeServices = true;
@ -42,3 +55,37 @@ export async function promote(ignoreSslErrors?: boolean) {
core.warning('Exception occurred while deleting canary and baseline workloads. Exception: ' + ex);
}
}
async function promoteBlueGreen(kubectl: Kubectl) {
// updated container images and pull secrets
let inputManifestFiles: string[] = getUpdatedManifestFiles(TaskInputParameters.manifests);
const manifestObjects = getManifestObjects(inputManifestFiles);
core.debug('deleting old deployment and making new ones');
let result;
if(isIngressRoute()) {
result = await promoteBlueGreenIngress(kubectl, manifestObjects);
} else if (isSMIRoute()) {
result = await promoteBlueGreenSMI(kubectl, manifestObjects);
} else {
result = await promoteBlueGreenService(kubectl, manifestObjects);
}
// checking stability of newly created deployments
const deployedManifestFiles = result.newFilePaths;
const resources: Resource[] = KubernetesObjectUtility.getResources(deployedManifestFiles, models.deploymentTypes.concat([models.DiscoveryAndLoadBalancerResource.service]));
await KubernetesManifestUtility.checkManifestStability(kubectl, resources);
core.debug('routing to new deployments');
if(isIngressRoute()) {
routeBlueGreenIngress(kubectl, null, manifestObjects.serviceNameMap, manifestObjects.serviceEntityList, manifestObjects.ingressEntityList);
deleteWorkloadsAndServicesWithLabel(kubectl, GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
} else if (isSMIRoute()) {
routeBlueGreenSMI(kubectl, NONE_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
deleteWorkloadsWithLabel(kubectl, GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList);
cleanupSMI(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
} else {
routeBlueGreenService(kubectl, NONE_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
deleteWorkloadsWithLabel(kubectl, GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList);
}
}

Просмотреть файл

@ -5,15 +5,26 @@ import * as SMICanaryDeploymentHelper from '../utilities/strategy-helpers/smi-ca
import { Kubectl } from '../kubectl-object-model';
import * as utils from '../utilities/manifest-utilities';
import * as TaskInputParameters from '../input-parameters';
import { rejectBlueGreenService } from '../utilities/strategy-helpers/service-blue-green-helper';
import { rejectBlueGreenIngress } from '../utilities/strategy-helpers/ingress-blue-green-helper';
import { rejectBlueGreenSMI } from '../utilities/strategy-helpers/smi-blue-green-helper'
import { isSMIRoute, isIngressRoute, isBlueGreenDeploymentStrategy } from '../utilities/strategy-helpers/blue-green-helper'
import { getManifestFiles } from '../utilities/strategy-helpers/deployment-helper'
export async function reject(ignoreSslErrors?: boolean) {
const kubectl = new Kubectl(await utils.getKubectl(), TaskInputParameters.namespace, ignoreSslErrors);
export async function reject() {
const kubectl = new Kubectl(await utils.getKubectl(), TaskInputParameters.namespace, true);
if (!canaryDeploymentHelper.isCanaryDeploymentStrategy()) {
core.debug('Strategy is not canary deployment. Invalid request.');
throw ('InvalidRejectActionDeploymentStrategy');
if (canaryDeploymentHelper.isCanaryDeploymentStrategy()) {
await rejectCanary(kubectl);
} else if (isBlueGreenDeploymentStrategy()) {
await rejectBlueGreen(kubectl);
} else {
core.debug('Strategy is not canary or blue-green deployment. Invalid request.');
throw ('InvalidDeletetActionDeploymentStrategy');
}
}
async function rejectCanary(kubectl: Kubectl) {
let includeServices = false;
if (canaryDeploymentHelper.isSMICanaryStrategy()) {
core.debug('Reject deployment with SMI canary strategy');
@ -24,3 +35,14 @@ export async function reject(ignoreSslErrors?: boolean) {
core.debug('Deployment strategy selected is Canary. Deleting baseline and canary workloads.');
canaryDeploymentHelper.deleteCanaryDeployment(kubectl, TaskInputParameters.manifests, includeServices);
}
async function rejectBlueGreen(kubectl: Kubectl) {
let inputManifestFiles: string[] = getManifestFiles(TaskInputParameters.manifests);
if(isIngressRoute()) {
await rejectBlueGreenIngress(kubectl, inputManifestFiles);
} else if (isSMIRoute()) {
await rejectBlueGreenSMI(kubectl, inputManifestFiles);
} else {
await rejectBlueGreenService(kubectl, inputManifestFiles);
}
}

Просмотреть файл

@ -9,6 +9,8 @@ export const manifests = core.getInput('manifests').split('\n');
export const canaryPercentage: string = core.getInput('percentage');
export const deploymentStrategy: string = core.getInput('strategy');
export const trafficSplitMethod: string = core.getInput('traffic-split-method');
export const routeMethod: string = core.getInput('route-method');
export const versionSwitchBuffer: string = core.getInput('version-switch-buffer');
export const baselineAndCanaryReplicas: string = core.getInput('baseline-and-canary-replicas');
export const args: string = core.getInput('arguments');
export const forceDeployment: boolean = core.getInput('force').toLowerCase() == 'true';
@ -39,3 +41,14 @@ try {
core.setFailed("Enter a valid 'baseline-and-canary-replicas' integer value");
process.exit(1);
}
try {
const pe = parseInt(versionSwitchBuffer);
if (pe < 0 || pe > 300) {
core.setFailed('Invalid buffer time, valid version-switch-buffer is a value more than or equal to 0 and lesser than or equal 300');
process.exit(1);
}
} catch (ex) {
core.setFailed("Enter a valid 'version-switch-buffer' integer value");
process.exit(1);
}

Просмотреть файл

@ -67,10 +67,10 @@ export async function run() {
await deploy(new Kubectl(kubectlPath, namespace), manifests, strategy);
}
else if (action === 'promote') {
await promote(true);
await promote();
}
else if (action === 'reject') {
await reject(true);
await reject();
}
else {
core.setFailed('Not a valid action. The allowed actions are deploy, promote, reject');

Просмотреть файл

@ -1,9 +1,15 @@
'use strict';
import * as core from '@actions/core';
import * as fs from 'fs';
import * as yaml from 'js-yaml';
import * as path from 'path';
import * as kubectlutility from './kubectl-util';
import * as io from '@actions/io';
import { isEqual } from "./utility";
import * as fileHelper from './files-helper';
import * as KubernetesObjectUtility from './resource-object-utility';
import * as TaskInputParameters from '../input-parameters';
export function getManifestFiles(manifestFilePaths: string[]): string[] {
if (!manifestFilePaths) {
@ -192,21 +198,34 @@ function substituteImageNameInSpecContent(currentString: string, imageName: stri
}, '');
}
export function updateContainerImagesInManifestFiles(contents, containers: string[]): string {
function updateContainerImagesInManifestFiles(filePaths: string[], containers: string[]): string[] {
if (!!containers && containers.length > 0) {
const newFilePaths = [];
const tempDirectory = fileHelper.getTempDirectory();
filePaths.forEach((filePath: string) => {
let contents = fs.readFileSync(filePath).toString();
containers.forEach((container: string) => {
let imageName = container.split(':')[0];
if (imageName.indexOf('@') > 0) {
imageName = imageName.split('@')[0];
}
if (contents.indexOf(imageName) > 0) {
contents = substituteImageNameInSpecContent(contents, imageName, container);
contents = substituteImageNameInSpecFile(contents, imageName, container);
}
});
const fileName = path.join(tempDirectory, path.basename(filePath));
fs.writeFileSync(
path.join(fileName),
contents
);
newFilePaths.push(fileName);
});
return newFilePaths;
}
return contents;
return filePaths;
}
export function updateImagePullSecrets(inputObject: any, newImagePullSecrets: string[]) {
@ -229,6 +248,44 @@ export function updateImagePullSecrets(inputObject: any, newImagePullSecrets: st
setImagePullSecrets(inputObject, existingImagePullSecretObjects);
}
function updateImagePullSecretsInManifestFiles(filePaths: string[], imagePullSecrets: string[]): string[] {
if (!!imagePullSecrets && imagePullSecrets.length > 0) {
const newObjectsList = [];
filePaths.forEach((filePath: string) => {
const fileContents = fs.readFileSync(filePath).toString();
yaml.safeLoadAll(fileContents, function (inputObject: any) {
if (!!inputObject && !!inputObject.kind) {
const kind = inputObject.kind;
if (KubernetesObjectUtility.isWorkloadEntity(kind)) {
KubernetesObjectUtility.updateImagePullSecrets(inputObject, imagePullSecrets, false);
}
newObjectsList.push(inputObject);
}
});
});
core.debug('New K8s objects after addin imagePullSecrets are :' + JSON.stringify(newObjectsList));
const newFilePaths = fileHelper.writeObjectsToFile(newObjectsList);
return newFilePaths;
}
return filePaths;
}
export function getUpdatedManifestFiles (manifestFilePaths: string[]) {
let inputManifestFiles: string[] = getManifestFiles(manifestFilePaths);
if (!inputManifestFiles || inputManifestFiles.length === 0) {
throw new Error(`ManifestFileNotFound : ${manifestFilePaths}`);
}
// artifact substitution
inputManifestFiles = updateContainerImagesInManifestFiles(inputManifestFiles, TaskInputParameters.containers);
// imagePullSecrets addition
inputManifestFiles = updateImagePullSecretsInManifestFiles(inputManifestFiles, TaskInputParameters.imagePullSecrets);
return inputManifestFiles;
}
const workloadTypes: string[] = ['deployment', 'replicaset', 'daemonset', 'pod', 'statefulset', 'job', 'cronjob'];
export function isWorkloadEntity(kind: string): boolean {

Просмотреть файл

@ -5,6 +5,7 @@ import * as yaml from 'js-yaml';
import { Resource } from '../kubectl-object-model';
import { KubernetesWorkload, deploymentTypes, workloadTypes } from '../constants';
import { StringComparer, isEqual } from './string-comparison';
const INGRESS = "Ingress";
export function isDeploymentEntity(kind: string): boolean {
if (!kind) {
@ -34,6 +35,14 @@ export function isServiceEntity(kind: string): boolean {
return isEqual("Service", kind, StringComparer.OrdinalIgnoreCase);
}
export function isIngressEntity(kind: string): boolean {
if (!kind) {
throw('ResourceKindNotDefined');
}
return isEqual(INGRESS, kind, StringComparer.OrdinalIgnoreCase);
}
export function getReplicaCount(inputObject: any): any {
if (!inputObject) {
throw ('NullInputObject');

Просмотреть файл

@ -0,0 +1,329 @@
'use strict';
import * as core from '@actions/core';
import * as fs from 'fs';
import * as yaml from 'js-yaml';
import { checkForErrors, sleep } from '../utility';
import { Kubectl } from '../../kubectl-object-model';
import { KubernetesWorkload } from '../../constants';
import * as fileHelper from '../files-helper';
import * as helper from '../resource-object-utility';
import * as TaskInputParameters from '../../input-parameters';
import { routeBlueGreenService } from './service-blue-green-helper';
import { routeBlueGreenIngress } from './ingress-blue-green-helper';
import { routeBlueGreenSMI } from './smi-blue-green-helper';
export const BLUE_GREEN_DEPLOYMENT_STRATEGY = 'BLUE-GREEN';
export const GREEN_LABEL_VALUE = 'green';
export const NONE_LABEL_VALUE = 'None';
export const BLUE_GREEN_VERSION_LABEL = 'k8s.deploy.color';
export const GREEN_SUFFIX = '-green';
export const STABLE_SUFFIX = '-stable'
const INGRESS_ROUTE = 'INGRESS';
const SMI_ROUTE = 'SMI';
export function isBlueGreenDeploymentStrategy() {
const deploymentStrategy = TaskInputParameters.deploymentStrategy;
return deploymentStrategy && deploymentStrategy.toUpperCase() === BLUE_GREEN_DEPLOYMENT_STRATEGY;
}
export function isIngressRoute(): boolean {
const routeMethod = TaskInputParameters.routeMethod;
return routeMethod && routeMethod.toUpperCase() === INGRESS_ROUTE;
}
export function isSMIRoute(): boolean {
const routeMethod = TaskInputParameters.routeMethod;
return routeMethod && routeMethod.toUpperCase() === SMI_ROUTE;
}
export async function routeBlueGreen(kubectl: Kubectl, inputManifestFiles: string[]) {
// get buffer time
let bufferTime: number = parseInt(TaskInputParameters.versionSwitchBuffer);
//logging start of buffer time
let dateNow = new Date();
console.log('starting buffer time of '+bufferTime+' minute/s at '+dateNow.toISOString()+' UTC');
// waiting
await sleep(bufferTime*1000*60);
// logging end of buffer time
dateNow = new Date();
console.log('stopping buffer time of '+bufferTime+' minute/s at '+dateNow.toISOString()+' UTC');
const manifestObjects = getManifestObjects(inputManifestFiles);
// routing to new deployments
if (isIngressRoute()) {
routeBlueGreenIngress(kubectl, GREEN_LABEL_VALUE, manifestObjects.serviceNameMap, manifestObjects.serviceEntityList, manifestObjects.ingressEntityList);
} else if (isSMIRoute()) {
routeBlueGreenSMI(kubectl, GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
} else {
routeBlueGreenService(kubectl, GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
}
}
export function deleteWorkloadsWithLabel(kubectl: Kubectl, deleteLabel: string, deploymentEntityList: any[]) {
let resourcesToDelete = []
deploymentEntityList.forEach((inputObject) => {
const name = inputObject.metadata.name;
const kind = inputObject.kind;
if (deleteLabel === NONE_LABEL_VALUE) {
// if dellabel is none, deletes stable deployments
const resourceToDelete = { name : name, kind : kind};
resourcesToDelete.push(resourceToDelete);
} else {
// if dellabel is not none, then deletes new green deployments
const resourceToDelete = { name : getBlueGreenResourceName(name, GREEN_SUFFIX), kind : kind };
resourcesToDelete.push(resourceToDelete);
}
});
// deletes the deployments
deleteObjects(kubectl, resourcesToDelete);
}
export function cleanUp(kubectl: Kubectl, deploymentEntityList: any[], serviceEntityList: any[]) {
// checks if services has some stable deployments to target or deletes them too
let deleteList = [];
deploymentEntityList.forEach((deploymentObject) => {
const existingDeploy = fetchResource(kubectl, deploymentObject.kind, deploymentObject.metadata.name);
if (!existingDeploy) {
serviceEntityList.forEach((serviceObject) => {
const serviceSelector: string = getServiceSelector(serviceObject);
const matchLabels: string = getDeploymentMatchLabels(deploymentObject);
if (!!serviceSelector && !!matchLabels && isServiceSelectorSubsetOfMatchLabel(serviceSelector, matchLabels)) {
const resourceToDelete = { name : serviceObject.metadata.name, kind : serviceObject.kind };
deleteList.push(resourceToDelete);
}
});
}
});
// delete service not targeting a deployment
deleteObjects(kubectl, deleteList);
}
export function deleteWorkloadsAndServicesWithLabel(kubectl: Kubectl, deleteLabel: string, deploymentEntityList: any[], serviceEntityList: any[]) {
// need to delete services and deployments
const deletionEntitiesList = deploymentEntityList.concat(serviceEntityList);
let resourcesToDelete = []
deletionEntitiesList.forEach((inputObject) => {
const name = inputObject.metadata.name;
const kind = inputObject.kind;
if (deleteLabel === NONE_LABEL_VALUE) {
// if not dellabel, delete stable objects
const resourceToDelete = { name : name, kind : kind};
resourcesToDelete.push(resourceToDelete);
} else {
// else delete green labels
const resourceToDelete = { name : getBlueGreenResourceName(name, GREEN_SUFFIX), kind : kind };
resourcesToDelete.push(resourceToDelete);
}
});
deleteObjects(kubectl, resourcesToDelete);
}
export function deleteObjects(kubectl: Kubectl, deleteList: any[]) {
// delete services and deployments
deleteList.forEach((delObject) => {
try {
const result = kubectl.delete([delObject.kind, delObject.name]);
checkForErrors([result]);
} catch (ex) {
// Ignore failures of delete if doesn't exist
}
});
}
export function getSuffix(label: string): string {
if(label === GREEN_LABEL_VALUE) {
return GREEN_SUFFIX
} else {
return '';
}
}
// other common functions
export function getManifestObjects (filePaths: string[]): any {
const deploymentEntityList = [];
const serviceEntityList = [];
const ingressEntityList = [];
const otherEntitiesList = [];
filePaths.forEach((filePath: string) => {
const fileContents = fs.readFileSync(filePath);
yaml.safeLoadAll(fileContents, function (inputObject) {
if(!!inputObject) {
const kind = inputObject.kind;
if (helper.isDeploymentEntity(kind)) {
deploymentEntityList.push(inputObject);
} else if (helper.isServiceEntity(kind)) {
serviceEntityList.push(inputObject);
} else if (helper.isIngressEntity(kind)) {
ingressEntityList.push(inputObject);
} else {
otherEntitiesList.push(inputObject);
}
}
});
})
let serviceNameMap = new Map<string, string>();
// find all services and add their names with blue green suffix
serviceEntityList.forEach(inputObject => {
const name = inputObject.metadata.name;
serviceNameMap.set(name, getBlueGreenResourceName(name, GREEN_SUFFIX));
});
return { serviceEntityList: serviceEntityList, serviceNameMap: serviceNameMap, deploymentEntityList: deploymentEntityList, ingressEntityList: ingressEntityList, otherObjects: otherEntitiesList };
}
export function isServiceRouted(serviceObject: any[], deploymentEntityList: any[]): boolean {
let shouldBeRouted: boolean = false;
const serviceSelector: string = getServiceSelector(serviceObject);
if (!!serviceSelector) {
deploymentEntityList.every((depObject) => {
// finding if there is a deployment in the given manifests the service targets
const matchLabels: string = getDeploymentMatchLabels(depObject);
if (!!matchLabels && isServiceSelectorSubsetOfMatchLabel(serviceSelector, matchLabels)) {
shouldBeRouted = true;
return false;
}
});
}
return shouldBeRouted;
}
export function createWorkloadsWithLabel(kubectl: Kubectl, deploymentObjectList: any[], nextLabel: string) {
const newObjectsList = [];
deploymentObjectList.forEach((inputObject) => {
// creating deployment with label
const newBlueGreenObject = getNewBlueGreenObject(inputObject, nextLabel);
core.debug('New blue-green object is: ' + JSON.stringify(newBlueGreenObject));
newObjectsList.push(newBlueGreenObject);
});
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
const result = kubectl.apply(manifestFiles);
return { 'result': result, 'newFilePaths': manifestFiles };
}
export function getNewBlueGreenObject(inputObject: any, labelValue: string): object {
const newObject = JSON.parse(JSON.stringify(inputObject));
// Updating name only if label is green label is given
if (labelValue === GREEN_LABEL_VALUE) {
newObject.metadata.name = getBlueGreenResourceName(inputObject.metadata.name, GREEN_SUFFIX);
}
// Adding labels and annotations
addBlueGreenLabelsAndAnnotations(newObject, labelValue);
return newObject;
}
export function addBlueGreenLabelsAndAnnotations(inputObject: any, labelValue: string) {
//creating the k8s.deploy.color label
const newLabels = new Map<string, string>();
newLabels[BLUE_GREEN_VERSION_LABEL] = labelValue;
// updating object labels and selector labels
helper.updateObjectLabels(inputObject, newLabels, false);
helper.updateSelectorLabels(inputObject, newLabels, false);
// updating spec labels if it is a service
if (!helper.isServiceEntity(inputObject.kind)) {
helper.updateSpecLabels(inputObject, newLabels, false);
}
}
export function getBlueGreenResourceName(name: string, suffix: string) {
return `${name}${suffix}`;
}
export function getSpecLabel(inputObject: any): string {
if(!!inputObject && inputObject.spec && inputObject.spec.selector && inputObject.spec.selector.matchLabels && inputObject.spec.selector.matchLabels[BLUE_GREEN_VERSION_LABEL]) {
return inputObject.spec.selector.matchLabels[BLUE_GREEN_VERSION_LABEL];
}
return '';
}
export function getDeploymentMatchLabels(deploymentObject: any): string {
if (!!deploymentObject && deploymentObject.kind.toUpperCase()==KubernetesWorkload.pod.toUpperCase() && !!deploymentObject.metadata && !!deploymentObject.metadata.labels) {
return JSON.stringify(deploymentObject.metadata.labels);
} else if (!!deploymentObject && deploymentObject.spec && deploymentObject.spec.selector && deploymentObject.spec.selector.matchLabels) {
return JSON.stringify(deploymentObject.spec.selector.matchLabels);
}
return '';
}
export function getServiceSelector(serviceObject: any): string {
if (!!serviceObject && serviceObject.spec && serviceObject.spec.selector) {
return JSON.stringify(serviceObject.spec.selector);
} else return '';
}
export function isServiceSelectorSubsetOfMatchLabel(serviceSelector: string, matchLabels: string): boolean {
let serviceSelectorMap = new Map();
let matchLabelsMap = new Map();
JSON.parse(serviceSelector, (key, value) => {
serviceSelectorMap.set(key, value);
});
JSON.parse(matchLabels, (key, value) => {
matchLabelsMap.set(key, value);
});
let isMatch = true;
serviceSelectorMap.forEach((value, key) => {
if (!!key && (!matchLabelsMap.has(key) || matchLabelsMap.get(key)) != value) {
isMatch = false;
}
});
return isMatch;
}
export function fetchResource(kubectl: Kubectl, kind: string, name: string) {
const result = kubectl.getResource(kind, name);
if (result == null || !!result.stderr) {
return null;
}
if (!!result.stdout) {
const resource = JSON.parse(result.stdout);
try {
UnsetsClusterSpecficDetails(resource);
return resource;
} catch (ex) {
core.debug('Exception occurred while Parsing ' + resource + ' in Json object');
core.debug(`Exception:${ex}`);
}
}
return null;
}
function UnsetsClusterSpecficDetails(resource: any) {
if (resource == null) {
return;
}
// Unsets the cluster specific details in the object
if (!!resource) {
const metadata = resource.metadata;
const status = resource.status;
if (!!metadata) {
const newMetadata = {
'annotations': metadata.annotations,
'labels': metadata.labels,
'name': metadata.name
};
resource.metadata = newMetadata;
}
if (!!status) {
resource.status = {};
}
}
}

Просмотреть файл

@ -1,8 +1,6 @@
'use strict';
import * as fs from 'fs';
import * as path from 'path';
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';
@ -13,30 +11,32 @@ import * as utils from '../manifest-utilities';
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 { deployPodCanary } from './pod-canary-deployment-helper';
import { deploySMICanary } from './smi-canary-deployment-helper';
import { checkForErrors } 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';
import { deployBlueGreenSMI } from './smi-blue-green-helper';
export async function deploy(kubectl: Kubectl, manifestFilePaths: string[], deploymentStrategy: string) {
// get manifest files
let inputManifestFiles: string[] = getManifestFiles(manifestFilePaths);
// artifact substitution
inputManifestFiles = updateContainerImagesInManifestFiles(inputManifestFiles, TaskInputParameters.containers);
// imagePullSecrets addition
inputManifestFiles = updateImagePullSecretsInManifestFiles(inputManifestFiles, TaskInputParameters.imagePullSecrets);
let inputManifestFiles: string[] = getUpdatedManifestFiles(manifestFilePaths);
// deployment
const deployedManifestFiles = deployManifests(inputManifestFiles, kubectl, isCanaryDeploymentStrategy(deploymentStrategy));
const deployedManifestFiles = deployManifests(inputManifestFiles, kubectl, isCanaryDeploymentStrategy(deploymentStrategy), isBlueGreenDeploymentStrategy());
// check manifest stability
const resourceTypes: Resource[] = KubernetesObjectUtility.getResources(deployedManifestFiles, models.deploymentTypes.concat([KubernetesConstants.DiscoveryAndLoadBalancerResource.service]));
await checkManifestStability(kubectl, resourceTypes);
// route blue-green deployments
if (isBlueGreenDeploymentStrategy()) {
await routeBlueGreen(kubectl, inputManifestFiles);
}
// print ingress resources
const ingressResources: Resource[] = KubernetesObjectUtility.getResources(deployedManifestFiles, [KubernetesConstants.DiscoveryAndLoadBalancerResource.ingress]);
ingressResources.forEach(ingressResource => {
@ -44,7 +44,7 @@ export async function deploy(kubectl: Kubectl, manifestFilePaths: string[], depl
});
}
function getManifestFiles(manifestFilePaths: string[]): string[] {
export function getManifestFiles(manifestFilePaths: string[]): string[] {
const files: string[] = utils.getManifestFiles(manifestFilePaths);
if (files == null || files.length === 0) {
@ -54,7 +54,7 @@ function getManifestFiles(manifestFilePaths: string[]): string[] {
return files;
}
function deployManifests(files: string[], kubectl: Kubectl, isCanaryDeploymentStrategy: boolean): string[] {
function deployManifests(files: string[], kubectl: Kubectl, isCanaryDeploymentStrategy: boolean, isBlueGreenDeploymentStrategy: boolean): string[] {
let result;
if (isCanaryDeploymentStrategy) {
let canaryDeploymentOutput: any;
@ -65,6 +65,17 @@ function deployManifests(files: string[], kubectl: Kubectl, isCanaryDeploymentSt
}
result = canaryDeploymentOutput.result;
files = canaryDeploymentOutput.newFilePaths;
} else if (isBlueGreenDeploymentStrategy) {
let blueGreenDeploymentOutput: any;
if (isIngressRoute()) {
blueGreenDeploymentOutput = deployBlueGreenIngress(kubectl, files);
} else if (isSMIRoute()) {
blueGreenDeploymentOutput = deployBlueGreenSMI(kubectl, files);
} else {
blueGreenDeploymentOutput = deployBlueGreenService(kubectl, files);
}
result = blueGreenDeploymentOutput.result;
files = blueGreenDeploymentOutput.newFilePaths;
} else {
if (canaryDeploymentHelper.isSMICanaryStrategy()) {
const updatedManifests = appendStableVersionLabelToResource(files, kubectl);
@ -104,58 +115,6 @@ async function checkManifestStability(kubectl: Kubectl, resources: Resource[]):
await KubernetesManifestUtility.checkManifestStability(kubectl, resources);
}
function updateContainerImagesInManifestFiles(filePaths: string[], containers: string[]): string[] {
if (!!containers && containers.length > 0) {
const newFilePaths = [];
const tempDirectory = fileHelper.getTempDirectory();
filePaths.forEach((filePath: string) => {
let contents = fs.readFileSync(filePath).toString();
containers.forEach((container: string) => {
let imageName = container.split(':')[0];
if (imageName.indexOf('@') > 0) {
imageName = imageName.split('@')[0];
}
if (contents.indexOf(imageName) > 0) {
contents = utils.substituteImageNameInSpecFile(contents, imageName, container);
}
});
const fileName = path.join(tempDirectory, path.basename(filePath));
fs.writeFileSync(
path.join(fileName),
contents
);
newFilePaths.push(fileName);
});
return newFilePaths;
}
return filePaths;
}
function updateImagePullSecretsInManifestFiles(filePaths: string[], imagePullSecrets: string[]): string[] {
if (!!imagePullSecrets && imagePullSecrets.length > 0) {
const newObjectsList = [];
filePaths.forEach((filePath: string) => {
const fileContents = fs.readFileSync(filePath).toString();
yaml.safeLoadAll(fileContents, function (inputObject: any) {
if (!!inputObject && !!inputObject.kind) {
const kind = inputObject.kind;
if (KubernetesObjectUtility.isWorkloadEntity(kind)) {
KubernetesObjectUtility.updateImagePullSecrets(inputObject, imagePullSecrets, false);
}
newObjectsList.push(inputObject);
}
});
});
core.debug('New K8s objects after addin imagePullSecrets are :' + JSON.stringify(newObjectsList));
const newFilePaths = fileHelper.writeObjectsToFile(newObjectsList);
return newFilePaths;
}
return filePaths;
}
function isCanaryDeploymentStrategy(deploymentStrategy: string): boolean {
return deploymentStrategy != null && deploymentStrategy.toUpperCase() === canaryDeploymentHelper.CANARY_DEPLOYMENT_STRATEGY.toUpperCase();
}

Просмотреть файл

@ -0,0 +1,158 @@
'use strict';
import * as core from '@actions/core';
import { Kubectl } from '../../kubectl-object-model';
import * as fileHelper from '../files-helper';
import { createWorkloadsWithLabel, getManifestObjects, getNewBlueGreenObject, addBlueGreenLabelsAndAnnotations, deleteWorkloadsAndServicesWithLabel, fetchResource } from './blue-green-helper';
import { GREEN_LABEL_VALUE, NONE_LABEL_VALUE, BLUE_GREEN_VERSION_LABEL } from './blue-green-helper';
const BACKEND = 'BACKEND';
export function deployBlueGreenIngress(kubectl: Kubectl, filePaths: string[]) {
// get all kubernetes objects defined in manifest files
const manifestObjects = getManifestObjects(filePaths);
// create deployments with green label value
const result = createWorkloadsWithLabel(kubectl, manifestObjects.deploymentEntityList, GREEN_LABEL_VALUE);
// create new services and other objects
let newObjectsList = [];
manifestObjects.serviceEntityList.forEach(inputObject => {
const newBlueGreenObject = getNewBlueGreenObject(inputObject, GREEN_LABEL_VALUE);;
core.debug('New blue-green object is: ' + JSON.stringify(newBlueGreenObject));
newObjectsList.push(newBlueGreenObject);
});
newObjectsList = newObjectsList.concat(manifestObjects.otherObjects);
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
// return results to check for manifest stability
return result;
}
export async function promoteBlueGreenIngress(kubectl: Kubectl, manifestObjects) {
//checking if anything to promote
if (!validateIngressesState(kubectl, manifestObjects.ingressEntityList, manifestObjects.serviceNameMap)) {
throw('NotInPromoteStateIngress');
}
// create stable deployments with new configuration
const result = createWorkloadsWithLabel(kubectl, manifestObjects.deploymentEntityList, NONE_LABEL_VALUE);
// create stable services with new configuration
const newObjectsList = [];
manifestObjects.serviceEntityList.forEach((inputObject) => {
const newBlueGreenObject = getNewBlueGreenObject(inputObject, NONE_LABEL_VALUE);
core.debug('New blue-green object is: ' + JSON.stringify(newBlueGreenObject));
newObjectsList.push(newBlueGreenObject);
});
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
// returning deployments to check for rollout stability
return result;
}
export async function rejectBlueGreenIngress(kubectl: Kubectl, filePaths: string[]) {
// get all kubernetes objects defined in manifest files
const manifestObjects = getManifestObjects(filePaths);
// routing ingress to stables services
routeBlueGreenIngress(kubectl, null, manifestObjects.serviceNameMap, manifestObjects.serviceEntityList, manifestObjects.ingressEntityList);
// deleting green services and deployments
deleteWorkloadsAndServicesWithLabel(kubectl, GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
}
export function routeBlueGreenIngress(kubectl: Kubectl, nextLabel: string, serviceNameMap: Map<string, string>, serviceEntityList: any[], ingressEntityList: any[]) {
let newObjectsList = [];
if (!nextLabel) {
newObjectsList = newObjectsList.concat(ingressEntityList);
} else {
ingressEntityList.forEach((inputObject) => {
if (isIngressRouted(inputObject, serviceNameMap)) {
const newBlueGreenIngressObject = getUpdatedBlueGreenIngress(inputObject, serviceNameMap, GREEN_LABEL_VALUE);
newObjectsList.push(newBlueGreenIngressObject);
} else {
newObjectsList.push(inputObject);
}
});
}
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
}
export function validateIngressesState(kubectl: Kubectl, ingressEntityList: any[], serviceNameMap: Map<string, string>): boolean {
let areIngressesTargetingNewServices: boolean = true;
ingressEntityList.forEach((inputObject) => {
if (isIngressRouted(inputObject, serviceNameMap)) {
//querying existing ingress
let existingIngress = fetchResource(kubectl, inputObject.kind, inputObject.metadata.name);
if(!!existingIngress) {
let currentLabel: string;
// checking its label
try {
currentLabel = existingIngress.metadata.labels[BLUE_GREEN_VERSION_LABEL];
} catch {
// if no label exists, then not an ingress targeting green deployments
areIngressesTargetingNewServices = false;
}
if (currentLabel != GREEN_LABEL_VALUE) {
// if not green label, then wrong configuration
areIngressesTargetingNewServices = false;
}
} else {
// no ingress at all, so nothing to promote
areIngressesTargetingNewServices = false;
}
}
});
return areIngressesTargetingNewServices;
}
function isIngressRouted(ingressObject: any, serviceNameMap: Map<string, string>): boolean {
let isIngressRouted: boolean = false;
// sees if ingress targets a service in the given manifests
JSON.parse(JSON.stringify(ingressObject), (key, value) => {
if (key === 'serviceName' && serviceNameMap.has(value)) {
isIngressRouted = true;
}
return value;
});
return isIngressRouted;
}
export function getUpdatedBlueGreenIngress(inputObject: any, serviceNameMap: Map<string, string>, type: string): object {
if(!type) {
// returning original with no modifications
return inputObject;
}
const newObject = JSON.parse(JSON.stringify(inputObject));
// adding green labels and values
addBlueGreenLabelsAndAnnotations(newObject, type);
// Updating ingress labels
let finalObject = updateIngressBackend(newObject, serviceNameMap);
return finalObject;
}
export function updateIngressBackend(inputObject: any, serviceNameMap: Map<string, string>): any {
inputObject = JSON.parse(JSON.stringify(inputObject), (key, value) => {
if(key.toUpperCase() === BACKEND) {
let serviceName = value.serviceName;
if (serviceNameMap.has(serviceName)) {
// updating service name with corresponding bluegreen name only if service is provied in given manifests
value.serviceName = serviceNameMap.get(serviceName);
}
}
return value;
});
return inputObject;
}

Просмотреть файл

@ -0,0 +1,102 @@
'use strict';
import { Kubectl } from '../../kubectl-object-model';
import * as fileHelper from '../files-helper';
import { createWorkloadsWithLabel, getManifestObjects, addBlueGreenLabelsAndAnnotations, fetchResource, deleteWorkloadsWithLabel, cleanUp, isServiceRouted } from './blue-green-helper';
import { GREEN_LABEL_VALUE, NONE_LABEL_VALUE, BLUE_GREEN_VERSION_LABEL } from './blue-green-helper';
export function deployBlueGreenService(kubectl: Kubectl, filePaths: string[]) {
// get all kubernetes objects defined in manifest files
const manifestObjects = getManifestObjects(filePaths);
// create deployments with green label value
const result = createWorkloadsWithLabel(kubectl, manifestObjects.deploymentEntityList, GREEN_LABEL_VALUE);
// create other non deployment and non service entities
const newObjectsList = manifestObjects.otherObjects.concat(manifestObjects.ingressEntityList);
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
// returning deployment details to check for rollout stability
return result;
}
export async function promoteBlueGreenService(kubectl: Kubectl, manifestObjects) {
// checking if services are in the right state ie. targeting green deployments
if (!validateServicesState(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList)) {
throw('NotInPromoteState');
}
// creating stable deployments with new configurations
const result = createWorkloadsWithLabel(kubectl, manifestObjects.deploymentEntityList, NONE_LABEL_VALUE);
// returning deployment details to check for rollout stability
return result;
}
export async function rejectBlueGreenService(kubectl: Kubectl, filePaths: string[]) {
// get all kubernetes objects defined in manifest files
const manifestObjects = getManifestObjects(filePaths);
// routing to stable objects
routeBlueGreenService(kubectl, NONE_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
// seeing if we should even delete the service
cleanUp(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
// deleting the new deployments with green suffix
deleteWorkloadsWithLabel(kubectl, GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList);
}
export function routeBlueGreenService(kubectl: Kubectl, nextLabel: string, deploymentEntityList: any[], serviceEntityList: any[]) {
const newObjectsList = [];
serviceEntityList.forEach((serviceObject) => {
if (isServiceRouted(serviceObject, deploymentEntityList)) {
// if service is routed, point it to given label
const newBlueGreenServiceObject = getUpdatedBlueGreenService(serviceObject, nextLabel);
newObjectsList.push(newBlueGreenServiceObject);
} else {
// if service is not routed, just push the original service
newObjectsList.push(serviceObject);
}
});
// configures the services
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
}
// adding green labels to configure existing service
function getUpdatedBlueGreenService(inputObject: any, labelValue: string): object {
const newObject = JSON.parse(JSON.stringify(inputObject));
// Adding labels and annotations.
addBlueGreenLabelsAndAnnotations(newObject, labelValue);
return newObject;
}
export function validateServicesState(kubectl: Kubectl, deploymentEntityList: any[], serviceEntityList: any[]): boolean {
let areServicesGreen: boolean = true;
serviceEntityList.forEach((serviceObject) => {
if (isServiceRouted(serviceObject, deploymentEntityList)) {
// finding the existing routed service
const existingService = fetchResource(kubectl, serviceObject.kind, serviceObject.metadata.name);
if (!!existingService) {
let currentLabel: string = getServiceSpecLabel(existingService);
if(currentLabel != GREEN_LABEL_VALUE) {
// service should be targeting deployments with green label
areServicesGreen = false;
}
} else {
// service targeting deployment doesn't exist
areServicesGreen = false;
}
}
});
return areServicesGreen;
}
export function getServiceSpecLabel(inputObject: any): string {
if(!!inputObject && inputObject.spec && inputObject.spec.selector && inputObject.spec.selector[BLUE_GREEN_VERSION_LABEL]) {
return inputObject.spec.selector[BLUE_GREEN_VERSION_LABEL];
}
return '';
}

Просмотреть файл

@ -0,0 +1,197 @@
'use strict';
import { Kubectl } from '../../kubectl-object-model';
import * as kubectlUtils from '../kubectl-util';
import * as fileHelper from '../files-helper';
import { createWorkloadsWithLabel, getManifestObjects, fetchResource, deleteWorkloadsWithLabel, cleanUp, getNewBlueGreenObject, getBlueGreenResourceName, isServiceRouted, deleteObjects } from './blue-green-helper';
import { GREEN_LABEL_VALUE, NONE_LABEL_VALUE, GREEN_SUFFIX, STABLE_SUFFIX } from './blue-green-helper';
let trafficSplitAPIVersion = "";
const TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX = '-trafficsplit';
const TRAFFIC_SPLIT_OBJECT = 'TrafficSplit';
const MIN_VAL = '0';
const MAX_VAL = '100';
export function deployBlueGreenSMI(kubectl: Kubectl, filePaths: string[]) {
// get all kubernetes objects defined in manifest files
const manifestObjects = getManifestObjects(filePaths);
// creating services and other objects
const newObjectsList = manifestObjects.otherObjects.concat(manifestObjects.serviceEntityList).concat(manifestObjects.ingressEntityList);
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
// make extraservices and trafficsplit
setupSMI(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
// create new deloyments
const result = createWorkloadsWithLabel(kubectl, manifestObjects.deploymentEntityList, GREEN_LABEL_VALUE);
// return results to check for manifest stability
return result;
}
export async function promoteBlueGreenSMI(kubectl: Kubectl, manifestObjects) {
// checking if there is something to promote
if (!validateTrafficSplitsState(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList)) {
throw('NotInPromoteStateSMI')
}
// create stable deployments with new configuration
const result = createWorkloadsWithLabel(kubectl, manifestObjects.deploymentEntityList, NONE_LABEL_VALUE);
// return result to check for stability
return result;
}
export async function rejectBlueGreenSMI(kubectl: Kubectl, filePaths: string[]) {
// get all kubernetes objects defined in manifest files
const manifestObjects = getManifestObjects(filePaths);
// routing trafficsplit to stable deploymetns
routeBlueGreenSMI(kubectl, NONE_LABEL_VALUE, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
// deciding whether to delete services or not
cleanUp(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
// deleting rejected new bluegreen deplyments
deleteWorkloadsWithLabel(kubectl, GREEN_LABEL_VALUE, manifestObjects.deploymentEntityList);
//deleting trafficsplit and extra services
cleanupSMI(kubectl, manifestObjects.deploymentEntityList, manifestObjects.serviceEntityList);
}
export function setupSMI(kubectl: Kubectl, deploymentEntityList: any[], serviceEntityList: any[]) {
const newObjectsList = [];
const trafficObjectList = []
serviceEntityList.forEach((serviceObject) => {
if (isServiceRouted(serviceObject, deploymentEntityList)) {
// create a trafficsplit for service
trafficObjectList.push(serviceObject);
// setting up the services for trafficsplit
const newStableService = getSMIServiceResource(serviceObject, STABLE_SUFFIX);
const newGreenService = getSMIServiceResource(serviceObject, GREEN_SUFFIX);
newObjectsList.push(newStableService);
newObjectsList.push(newGreenService);
}
});
// creating services
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList);
kubectl.apply(manifestFiles);
// route to stable service
trafficObjectList.forEach((inputObject) => {
createTrafficSplitObject(kubectl, inputObject.metadata.name, NONE_LABEL_VALUE);
});
}
function createTrafficSplitObject(kubectl: Kubectl ,name: string, nextLabel: string): any {
// getting smi spec api version
trafficSplitAPIVersion = kubectlUtils.getTrafficSplitAPIVersion(kubectl);
// deciding weights based on nextlabel
let stableWeight: number;
let greenWeight: number;
if (nextLabel === GREEN_LABEL_VALUE) {
stableWeight = parseInt(MIN_VAL);
greenWeight = parseInt(MAX_VAL);
} else {
stableWeight = parseInt(MAX_VAL);
greenWeight = parseInt(MIN_VAL);
}
//traffic split json
const trafficSplitObject = `{
"apiVersion": "${trafficSplitAPIVersion}",
"kind": "TrafficSplit",
"metadata": {
"name": "${getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX)}"
},
"spec": {
"service": "${name}",
"backends": [
{
"service": "${getBlueGreenResourceName(name, STABLE_SUFFIX)}",
"weight": ${stableWeight}
},
{
"service": "${getBlueGreenResourceName(name, GREEN_SUFFIX)}",
"weight": ${greenWeight}
}
]
}
}`;
// creating trafficplit object
const trafficSplitManifestFile = fileHelper.writeManifestToFile(trafficSplitObject, TRAFFIC_SPLIT_OBJECT, getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX));
kubectl.apply(trafficSplitManifestFile);
}
export function getSMIServiceResource(inputObject: any, suffix: string): object {
const newObject = JSON.parse(JSON.stringify(inputObject));
if (suffix === STABLE_SUFFIX) {
// adding stable suffix to service name
newObject.metadata.name = getBlueGreenResourceName(inputObject.metadata.name, STABLE_SUFFIX)
return getNewBlueGreenObject(newObject, NONE_LABEL_VALUE);
} else {
// green label will be added for these
return getNewBlueGreenObject(newObject, GREEN_LABEL_VALUE);
}
}
export function routeBlueGreenSMI(kubectl: Kubectl, nextLabel: string, deploymentEntityList: any[], serviceEntityList: any[]) {
serviceEntityList.forEach((serviceObject) => {
if (isServiceRouted(serviceObject, deploymentEntityList)) {
// routing trafficsplit to given label
createTrafficSplitObject(kubectl, serviceObject.metadata.name, nextLabel);
}
});
}
export function validateTrafficSplitsState(kubectl: Kubectl, deploymentEntityList: any[], serviceEntityList: any[]): boolean {
let areTrafficSplitsInRightState: boolean = true;
serviceEntityList.forEach((serviceObject) => {
if (isServiceRouted(serviceObject, deploymentEntityList)) {
const name = serviceObject.metadata.name;
let trafficSplitObject = fetchResource(kubectl, TRAFFIC_SPLIT_OBJECT, getBlueGreenResourceName(name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX));
if (!trafficSplitObject) {
// no trafficplit exits
areTrafficSplitsInRightState = false;
}
trafficSplitObject = JSON.parse(JSON.stringify(trafficSplitObject));
trafficSplitObject.spec.backends.forEach(element => {
// checking if trafficsplit in right state to deploy
if (element.service === getBlueGreenResourceName(name, GREEN_SUFFIX)) {
if (element.weight != MAX_VAL) {
// green service should have max weight
areTrafficSplitsInRightState = false;
}
}
if (element.service === getBlueGreenResourceName(name, STABLE_SUFFIX)) {
if (element.weight != MIN_VAL) {
// stable service should have 0 weight
areTrafficSplitsInRightState = false;
}
}
});
}
});
return areTrafficSplitsInRightState;
}
export function cleanupSMI(kubectl: Kubectl, deploymentEntityList: any[], serviceEntityList: any[]) {
const deleteList = [];
serviceEntityList.forEach((serviceObject) => {
if (isServiceRouted(serviceObject, deploymentEntityList)) {
deleteList.push({name: getBlueGreenResourceName(serviceObject.metadata.name, GREEN_SUFFIX), kind: serviceObject.kind});
deleteList.push({name: getBlueGreenResourceName(serviceObject.metadata.name, STABLE_SUFFIX), kind: serviceObject.kind});
deleteList.push({name: getBlueGreenResourceName(serviceObject.metadata.name, TRAFFIC_SPLIT_OBJECT_NAME_SUFFIX), kind: TRAFFIC_SPLIT_OBJECT});
}
});
// deleting all objects
deleteObjects(kubectl, deleteList);
}