Annotation and Label changes to handle multiple workflows across branches(master)

This commit is contained in:
Koushik Dey
2020-09-21 13:28:51 +05:30
parent b9146889f3
commit 92589546e8
16 changed files with 627 additions and 120 deletions
+114
View File
@@ -0,0 +1,114 @@
// Taken from https://github.com/Azure/aks-set-context/blob/master/src/client.ts
import util = require("util");
import fs = require('fs');
import httpClient = require("typed-rest-client/HttpClient");
import * as core from '@actions/core';
var httpCallbackClient = new httpClient.HttpClient('GITHUB_RUNNER', null, {});
export enum StatusCodes {
OK = 200,
CREATED = 201,
ACCEPTED = 202,
UNAUTHORIZED = 401,
NOT_FOUND = 404,
INTERNAL_SERVER_ERROR = 500,
SERVICE_UNAVAILABLE = 503
}
export class WebRequest {
public method: string;
public uri: string;
// body can be string or ReadableStream
public body: string | NodeJS.ReadableStream;
public headers: any;
}
export class WebResponse {
public statusCode: number;
public statusMessage: string;
public headers: any;
public body: any;
}
export class WebRequestOptions {
public retriableErrorCodes?: string[];
public retryCount?: number;
public retryIntervalInSeconds?: number;
public retriableStatusCodes?: number[];
public retryRequestTimedout?: boolean;
}
export async function sendRequest(request: WebRequest, options?: WebRequestOptions): Promise<WebResponse> {
let i = 0;
let retryCount = options && options.retryCount ? options.retryCount : 5;
let retryIntervalInSeconds = options && options.retryIntervalInSeconds ? options.retryIntervalInSeconds : 2;
let retriableErrorCodes = options && options.retriableErrorCodes ? options.retriableErrorCodes : ["ETIMEDOUT", "ECONNRESET", "ENOTFOUND", "ESOCKETTIMEDOUT", "ECONNREFUSED", "EHOSTUNREACH", "EPIPE", "EA_AGAIN"];
let retriableStatusCodes = options && options.retriableStatusCodes ? options.retriableStatusCodes : [408, 409, 500, 502, 503, 504];
let timeToWait: number = retryIntervalInSeconds;
while (true) {
try {
if (request.body && typeof (request.body) !== 'string' && !request.body["readable"]) {
request.body = fs.createReadStream(request.body["path"]);
}
let response: WebResponse = await sendRequestInternal(request);
if (retriableStatusCodes.indexOf(response.statusCode) != -1 && ++i < retryCount) {
core.debug(util.format("Encountered a retriable status code: %s. Message: '%s'.", response.statusCode, response.statusMessage));
await sleepFor(timeToWait);
timeToWait = timeToWait * retryIntervalInSeconds + retryIntervalInSeconds;
continue;
}
return response;
}
catch (error) {
if (retriableErrorCodes.indexOf(error.code) != -1 && ++i < retryCount) {
core.debug(util.format("Encountered a retriable error:%s. Message: %s.", error.code, error.message));
await sleepFor(timeToWait);
timeToWait = timeToWait * retryIntervalInSeconds + retryIntervalInSeconds;
}
else {
if (error.code) {
core.debug("error code =" + error.code);
}
throw error;
}
}
}
}
export function sleepFor(sleepDurationInSeconds: number): Promise<any> {
return new Promise((resolve, reject) => {
setTimeout(resolve, sleepDurationInSeconds * 1000);
});
}
async function sendRequestInternal(request: WebRequest): Promise<WebResponse> {
core.debug(util.format("[%s]%s", request.method, request.uri));
var response: httpClient.HttpClientResponse = await httpCallbackClient.request(request.method, request.uri, request.body, request.headers);
return await toWebResponse(response);
}
async function toWebResponse(response: httpClient.HttpClientResponse): Promise<WebResponse> {
var res = new WebResponse();
if (response) {
res.statusCode = response.message.statusCode;
res.statusMessage = response.message.statusMessage;
res.headers = response.message.headers;
var body = await response.readBody();
if (body) {
try {
res.body = JSON.parse(body);
}
catch (error) {
core.debug("Could not parse response: " + JSON.stringify(error));
core.debug("Response: " + JSON.stringify(res.body));
res.body = body;
}
}
}
return res;
}
@@ -17,7 +17,7 @@ import { IExecSyncResult } from '../../utilities/tool-runner';
import { deployPodCanary } from './pod-canary-deployment-helper';
import { deploySMICanary } from './smi-canary-deployment-helper';
import { checkForErrors, annotateChildPods, annotateNamespace } from "../utility";
import { checkForErrors, annotateChildPods, getWorkflowFilePath, getLastSuccessfulRunSha } 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';
@@ -54,7 +54,7 @@ export async function deploy(kubectl: Kubectl, manifestFilePaths: string[], depl
core.debug("Unable to parse pods; Error: " + e);
}
annotateResources(deployedManifestFiles, kubectl, resourceTypes, allPods);
annotateAndLabelResources(deployedManifestFiles, kubectl, resourceTypes, allPods);
}
export function getManifestFiles(manifestFilePaths: string[]): string[] {
@@ -128,19 +128,36 @@ async function checkManifestStability(kubectl: Kubectl, resources: Resource[]):
await KubernetesManifestUtility.checkManifestStability(kubectl, resources);
}
function annotateResources(files: string[], kubectl: Kubectl, resourceTypes: Resource[], allPods: any) {
async function annotateAndLabelResources(files: string[], kubectl: Kubectl, resourceTypes: Resource[], allPods: any) {
const workflowFilePath = await getWorkflowFilePath(TaskInputParameters.githubToken);
const annotationKeyLabel = models.getWorkflowAnnotationKeyLabel(workflowFilePath);
annotateResources(files, kubectl, resourceTypes, allPods, annotationKeyLabel, workflowFilePath);
labelResources(files, kubectl, annotationKeyLabel);
}
function annotateResources(files: string[], kubectl: Kubectl, resourceTypes: Resource[], allPods: any, annotationKey: string, workflowFilePath: string) {
const annotateResults: IExecSyncResult[] = [];
annotateResults.push(annotateNamespace(kubectl, TaskInputParameters.namespace));
annotateResults.push(kubectl.annotateFiles(files, models.workflowAnnotations, true));
const lastSuccessSha = getLastSuccessfulRunSha(kubectl, TaskInputParameters.namespace, annotationKey);
let annotationKeyValStr = annotationKey + '=' + models.getWorkflowAnnotationsJson(lastSuccessSha, workflowFilePath);
annotateResults.push(kubectl.annotate('namespace', TaskInputParameters.namespace, annotationKeyValStr));
annotateResults.push(kubectl.annotateFiles(files, annotationKeyValStr));
resourceTypes.forEach(resource => {
if (resource.type.toUpperCase() !== models.KubernetesWorkload.pod.toUpperCase()) {
annotateChildPods(kubectl, resource.type, resource.name, allPods)
annotateChildPods(kubectl, resource.type, resource.name, annotationKeyValStr, allPods)
.forEach(execResult => annotateResults.push(execResult));
}
});
checkForErrors(annotateResults, true);
}
function labelResources(files: string[], kubectl: Kubectl, label: string) {
let workflowName = process.env.GITHUB_WORKFLOW;
workflowName = workflowName.startsWith('.github/workflows/') ?
workflowName.replace(".github/workflows/", "") : workflowName;
const labels = [`workflowFriendlyName=${workflowName}`, `workflow=${label}`];
checkForErrors([kubectl.labelFiles(files, labels)], true);
}
function isCanaryDeploymentStrategy(deploymentStrategy: string): boolean {
return deploymentStrategy != null && deploymentStrategy.toUpperCase() === canaryDeploymentHelper.CANARY_DEPLOYMENT_STRATEGY.toUpperCase();
}
+58 -26
View File
@@ -2,7 +2,8 @@ import * as os from 'os';
import * as core from '@actions/core';
import { IExecSyncResult } from './tool-runner';
import { Kubectl } from '../kubectl-object-model';
import { workflowAnnotations } from '../constants';
import { GitHubClient } from '../githubClient';
import { StatusCodes } from "./httpClient";
export function getExecutableExtension(): string {
if (os.type().match(/^Win/)) {
@@ -50,7 +51,61 @@ export function checkForErrors(execResults: IExecSyncResult[], warnIfError?: boo
}
}
export function annotateChildPods(kubectl: Kubectl, resourceType: string, resourceName: string, allPods): IExecSyncResult[] {
export function getLastSuccessfulRunSha(kubectl: Kubectl, namespaceName: string, annotationKey: string): string {
try {
const result = kubectl.getResource('namespace', namespaceName);
if (result) {
if (result.stderr) {
core.warning(`${result.stderr}`);
return process.env.GITHUB_SHA;
}
else if (result.stdout) {
const annotationsSet = JSON.parse(result.stdout).metadata.annotations;
if (annotationsSet && annotationsSet[annotationKey]) {
return JSON.parse(annotationsSet[annotationKey].replace(/'/g, '"')).commit;
}
else {
return 'NA';
}
}
}
}
catch (ex) {
core.warning(`Failed to get commits from cluster. ${JSON.stringify(ex)}`);
return '';
}
}
export async function getWorkflowFilePath(githubToken: string): Promise<string> {
let workflowFilePath = process.env.GITHUB_WORKFLOW;
if (!workflowFilePath.startsWith('.github/workflows/')) {
const githubClient = new GitHubClient(process.env.GITHUB_REPOSITORY, githubToken);
const response = await githubClient.getWorkflows();
if (response) {
if (response.statusCode == StatusCodes.OK
&& response.body
&& response.body.total_count) {
if (response.body.total_count > 0) {
for (let workflow of response.body.workflows) {
if (process.env.GITHUB_WORKFLOW === workflow.name) {
workflowFilePath = workflow.path;
break;
}
}
}
}
else if (response.statusCode != StatusCodes.OK) {
core.debug(`An error occured while getting list of workflows on the repo. Statuscode: ${response.statusCode}, StatusMessage: ${response.statusMessage}`);
}
}
else {
core.warning(`Failed to get response from workflow list API`);
}
}
return Promise.resolve(workflowFilePath);
}
export function annotateChildPods(kubectl: Kubectl, resourceType: string, resourceName: string, annotationKeyValStr: string, allPods): IExecSyncResult[] {
const commandExecutionResults = [];
let owner = resourceName;
if (resourceType.toLowerCase().indexOf('deployment') > -1) {
@@ -63,7 +118,7 @@ export function annotateChildPods(kubectl: Kubectl, resourceType: string, resour
if (owners) {
for (let ownerRef of owners) {
if (ownerRef.name === owner) {
commandExecutionResults.push(kubectl.annotate('pod', pod.metadata.name, workflowAnnotations, true));
commandExecutionResults.push(kubectl.annotate('pod', pod.metadata.name, annotationKeyValStr));
break;
}
}
@@ -74,29 +129,6 @@ export function annotateChildPods(kubectl: Kubectl, resourceType: string, resour
return commandExecutionResults;
}
export function annotateNamespace(kubectl: Kubectl, namespaceName: string): IExecSyncResult {
const result = kubectl.getResource('namespace', namespaceName);
if (!result) {
return { code: 1, stderr: 'Failed to get resource' } as IExecSyncResult;
}
else {
if (result.stderr) {
return result;
}
else if (result.stdout) {
const annotationsSet = JSON.parse(result.stdout).metadata.annotations;
if (annotationsSet && annotationsSet.runUri) {
if (annotationsSet.runUri.indexOf(process.env['GITHUB_REPOSITORY']) == -1) {
core.debug(`Skipping 'annotate namespace' as namespace annotated by other workflow`);
return { code: 0, stdout: '' } as IExecSyncResult;
}
}
return kubectl.annotate('namespace', namespaceName, workflowAnnotations, true);
}
}
}
export function sleep(timeout: number) {
return new Promise(resolve => setTimeout(resolve, timeout));
}