Skip to content

Commit 98a2e11

Browse files
authored
Updated helm deploy task to extract and push deployment metadata to e… (#11327)
* Updated helm deploy task to extract and push deployment metadata to evidence store * Handling manifest url as per build source * Handling file path when configuration file path is provided and arguments are null * Review comments
1 parent 82deb88 commit 98a2e11

File tree

21 files changed

+463
-119
lines changed

21 files changed

+463
-119
lines changed

Tasks/Common/kubernetes-common-v2/image-metadata-helper.ts

Lines changed: 139 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,42 @@
11
import * as tl from "azure-pipelines-task-lib/task";
22
import * as util from "util";
3+
import * as yaml from 'js-yaml';
34

45
const matchPatternForImageName = new RegExp(/\:\/\/(.+?)\@/);
56
const matchPatternForDigest = new RegExp(/\@sha256\:(.+)/);
67
const matchPatternForFileArgument = new RegExp(/-f\s|-filename\s/);
78
const matchPatternForServerUrl = new RegExp(/https\:\/\/(.+)/);
9+
const matchPatternForSource = new RegExp(/source:(.+)/ig);
10+
const matchPatternForChartPath = new RegExp(/chart path:(.+)/i);
811
const orgUrl = tl.getVariable('System.TeamFoundationCollectionUri');
912
const build = "build";
1013
const hostType = tl.getVariable("System.HostType").toLowerCase();
1114
const isBuild = hostType === build;
1215
const deploymentTypes: string[] = ["deployment", "replicaset", "daemonset", "pod", "statefulset"];
16+
const workingDirectory = tl.getVariable("System.DefaultWorkingDirectory");
17+
const branch = tl.getVariable("Build.SourceBranchName") || tl.getVariable("Build.SourceBranch");
18+
const repositoryProvider = tl.getVariable("Build.Repository.Provider");
19+
const repositoryUrl = tl.getVariable("Build.Repository.Uri");
1320

1421
// ToDo: Add UTs for public methods
15-
export function getDeploymentMetadata(deploymentObject: any, allPods: any, deploymentStrategy: string, clusterInfo: any, manifestFilePaths?: string[]): any {
22+
export function getDeploymentMetadata(deploymentObject: any, allPods: any, deploymentStrategy: string, clusterInfo: any, manifestUrls: string[]): any {
1623
let imageIds: string[] = [];
24+
let containers = [];
1725
let kind: string = deploymentObject.kind;
1826
try {
1927
if (isPodEntity(kind)) {
20-
imageIds = getImageIdsForPod(deploymentObject);
28+
containers = deploymentObject.spec.containers;
2129
}
2230
else {
23-
let containers = deploymentObject.spec.template.spec.containers;
24-
if (containers && containers.length > 0) {
25-
containers.forEach(container => {
26-
// Filter all pods using the container names in this deployment,
27-
// and get the imageIds from pod status
28-
imageIds = getImageIdsForPodsInDeployment(container.name, allPods.items);
29-
});
30-
}
31+
containers = deploymentObject.spec.template.spec.containers;
32+
}
33+
34+
if (containers && containers.length > 0) {
35+
containers.forEach(container => {
36+
// Filter all pods using the container names in this deployment,
37+
// and get the imageIds from pod status
38+
imageIds = getImageIdsForPodsInDeployment(container.name, allPods.items);
39+
});
3140
}
3241
}
3342
catch (e) {
@@ -42,14 +51,8 @@ export function getDeploymentMetadata(deploymentObject: any, allPods: any, deplo
4251
relatedUrls.push(clusterUrl);
4352
}
4453

45-
if (manifestFilePaths) {
46-
relatedUrls.push(...manifestFilePaths);
47-
}
48-
else {
49-
let manifestPaths = getManifestFilePaths();
50-
if (manifestPaths.length > 0) {
51-
relatedUrls.push(...manifestPaths);
52-
}
54+
if (manifestUrls.length > 0) {
55+
relatedUrls.push(...manifestUrls);
5356
}
5457

5558
const metadataDetails = {
@@ -192,19 +195,130 @@ function getServerUrl(clusterInfo: any): string {
192195
return serverUrl;
193196
}
194197

195-
function getManifestFilePaths(): string[] {
196-
let manifestFilePaths: string[] = [];
197-
const commandArguments = tl.getInput("arguments", false);
198-
const filePathMatch: string[] = commandArguments.split(matchPatternForFileArgument);
199-
if (filePathMatch && filePathMatch.length >= 0) {
198+
export function extractManifestsFromHelmOutput(helmOutput: string): any {
199+
let manifestObjects = [];
200+
let manifestFiles = "";
201+
// The output stream contains the manifest file between the manifest and last deployed fields
202+
const manifestString = "manifest:";
203+
const lastDeployedString = "last deployed:";
204+
let indexOfManifests = helmOutput.toLowerCase().indexOf(manifestString);
205+
let indexOfLastDeployed = helmOutput.toLowerCase().indexOf(lastDeployedString);
206+
if (indexOfManifests >= 0 && indexOfLastDeployed >= 0) {
207+
manifestFiles = helmOutput.substring(indexOfManifests + manifestString.length, indexOfLastDeployed);
208+
}
209+
210+
if (manifestFiles) {
211+
// Each of the source manifests is separated in output stream via string '---'
212+
const files = manifestFiles.split("---");
213+
files.forEach(file => {
214+
file = file.trim();
215+
if (file) {
216+
const parsedObject = yaml.safeLoad(file);
217+
manifestObjects.push(parsedObject);
218+
}
219+
});
220+
}
221+
222+
return manifestObjects;
223+
}
224+
225+
export function getManifestFileUrlsFromArgumentsInput(fileArgs: string): string[] {
226+
let manifestFileUrls: string[] = [];
227+
const filePathMatch: string[] = fileArgs.split(matchPatternForFileArgument);
228+
if (filePathMatch && filePathMatch.length > 0) {
200229
filePathMatch.forEach(manifestPath => {
201230
if (!!manifestPath) {
202-
manifestFilePaths.push(manifestPath.trim())
231+
if (manifestPath.startsWith("http") || manifestPath.startsWith("https:")) {
232+
manifestFileUrls.push(manifestPath);
233+
}
234+
else {
235+
manifestFileUrls.push(...getManifestUrls([manifestPath]));
236+
}
203237
}
204238
});
205239
}
206240

207-
return manifestFilePaths;
241+
return manifestFileUrls;
242+
}
243+
244+
export function getManifestFileUrlsFromHelmOutput(helmOutput: string): string[] {
245+
const chartType = tl.getInput("chartType", true);
246+
// Raw github links are supported only for chart names not chart paths
247+
if (chartType === "Name") {
248+
const chartName = tl.getInput("chartName", true);
249+
if (chartName.startsWith("http:") || chartName.startsWith("https:")) {
250+
return [chartName];
251+
}
252+
}
253+
254+
let manifestFilePaths: string[] = [];
255+
// Extract the chart directory
256+
const directoryName = getChartDirectoryName(helmOutput);
257+
// Extract all source paths; source path example - # Source: MyChart/templates/pod.yaml
258+
const filePathMatches = helmOutput.match(matchPatternForSource);
259+
if (filePathMatches && filePathMatches.length >= 1) {
260+
filePathMatches.forEach(filePathMatch => {
261+
// Strip the Chart name from source path to get the template path
262+
let indexOfTemplate = filePathMatch.toLowerCase().indexOf("templates");
263+
const templatePath = indexOfTemplate >= 0 ? filePathMatch.substr(indexOfTemplate) : filePathMatch;
264+
manifestFilePaths.push(directoryName + "/" + templatePath.trim());
265+
});
266+
}
267+
268+
return getManifestUrls(manifestFilePaths);
269+
}
270+
271+
export function getChartDirectoryName(helmOutput: string): string {
272+
// The output contains chart path in the following format - CHART PATH: C:\agent\_work\2\s\helm-chart-directory
273+
let directoryName = "";
274+
const chartPathMatch = helmOutput.match(matchPatternForChartPath);
275+
if (chartPathMatch && chartPathMatch.length >= 1) {
276+
let fullPath = chartPathMatch[1];
277+
let indexOfLastSeparator = fullPath.lastIndexOf("\\");
278+
directoryName = indexOfLastSeparator >= 0 ? fullPath.substr(indexOfLastSeparator + 1) : fullPath;
279+
}
280+
281+
return directoryName;
282+
}
283+
284+
export function getManifestUrls(manifestFilePaths: string[]): string[] {
285+
let manifestUrls = [];
286+
const branchName = getBranchName(branch);
287+
for (const path of manifestFilePaths) {
288+
let manifestUrl = "";
289+
let normalisedPath = path.indexOf(workingDirectory) === 0 ? path.substr(workingDirectory.length) : path;
290+
normalisedPath = normalisedPath.replace(/\\/g, "/");
291+
292+
if (repositoryProvider && (repositoryProvider.toLowerCase() === "githubenterprise" || repositoryProvider.toLowerCase() === "github")) {
293+
if (normalisedPath.indexOf("/") != 0) {
294+
// Prepend "/" if not present in path beginning as the path is appended as it is in manifest url to access github repo
295+
normalisedPath = "/" + normalisedPath;
296+
}
297+
298+
manifestUrl = repositoryUrl + "/blob/" + branchName + normalisedPath;
299+
}
300+
else if (repositoryProvider && repositoryProvider.toLowerCase() === "tfsgit") {
301+
if (normalisedPath.indexOf("/") === 0) {
302+
// Remove "/" from path if present in the beginning as we need to append path as a query string in manifest url to access tfs repo
303+
normalisedPath = normalisedPath.substr(1);
304+
}
305+
306+
manifestUrl = repositoryUrl + "?path=" + normalisedPath;
307+
}
308+
309+
manifestUrls.push(manifestUrl);
310+
}
311+
312+
return manifestUrls;
313+
}
314+
315+
function getBranchName(ref: string): string {
316+
const gitRefsHeadsPrefix = "refs/heads/";
317+
if (ref && ref.indexOf(gitRefsHeadsPrefix) === 0) {
318+
return ref.substr(gitRefsHeadsPrefix.length);
319+
}
320+
321+
return ref;
208322
}
209323

210324
function getPlatform(): string {

Tasks/HelmDeployV0/make.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
"type": "node",
1717
"dest" : "./",
1818
"compile" : true
19+
},
20+
{
21+
"module": "../Common/utility-common-v2",
22+
"type": "node",
23+
"dest" : "./",
24+
"compile" : true
1925
}
2026
],
2127
"rm": [

0 commit comments

Comments
 (0)