Skip to content

Commit 8c7fbfe

Browse files
aleksandrlevochkinDmitrii Bobreshev (Akvelon INC)
andauthored
[DownloadBuildArtifactsV0] Update to Node 16 using build config gen (#18751)
* Introduce Node16-225 for buildconfig - Switched config key from Node16-219 to Node16-225 - Added overriddenDirectoryName to set generated directory postfix * Introduce Node16-225 for buildconfig - Added deprecation option * Migrate task --------- Co-authored-by: Dmitrii Bobreshev (Akvelon INC) <[email protected]>
1 parent d3d7022 commit 8c7fbfe

File tree

77 files changed

+16632
-306
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+16632
-306
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Place files overridden for this config in this directory

Tasks/DownloadBuildArtifactsV0/_buildConfigs/Node16/package-lock.json

Lines changed: 3095 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "downloadbuildartifacts",
3+
"version": "0.1.0",
4+
"description": "Download Build Artifacts Task",
5+
"main": "download.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/Microsoft/azure-pipelines-tasks.git"
12+
},
13+
"author": "Microsoft Corporation",
14+
"license": "MIT",
15+
"bugs": {
16+
"url": "https://github.com/Microsoft/azure-pipelines-tasks/issues"
17+
},
18+
"homepage": "https://github.com/Microsoft/azure-pipelines-tasks#readme",
19+
"dependencies": {
20+
"@types/mocha": "^5.2.7",
21+
"@types/node": "^16.11.39",
22+
"artifact-engine": "^1.2.0",
23+
"azure-devops-node-api": "^10.2.2",
24+
"azure-pipelines-task-lib": "^4.4.0",
25+
"decompress-zip": "0.3.3"
26+
},
27+
"devDependencies": {
28+
"typescript": "4.0.2"
29+
}
30+
}

Tasks/DownloadBuildArtifactsV0/task.json

Lines changed: 303 additions & 303 deletions
Large diffs are not rendered by default.

Tasks/DownloadBuildArtifactsV0/task.loc.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"author": "Microsoft Corporation",
1010
"version": {
1111
"Major": 0,
12-
"Minor": 225,
13-
"Patch": 0
12+
"Minor": 226,
13+
"Patch": 1
1414
},
1515
"groups": [
1616
{
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Default|0.226.1
2+
Node16-225|0.226.0
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { IBaseHandlerConfig } from './HandlerConfigs';
2+
import { handlerCheckDownloadedFiles } from '../download_helper';
3+
import { ArtifactEngine } from 'artifact-engine/Engine';
4+
import { IArtifactProvider, ArtifactDownloadTicket } from 'artifact-engine/Models';
5+
import * as tl from 'azure-pipelines-task-lib/task';
6+
7+
/**
8+
* Base class for artifact download handlers
9+
*/
10+
export abstract class DownloadHandler {
11+
/**
12+
* @member {IBaseHandlerConfig} - contains info for generate source and destination providers
13+
* @access protected
14+
*/
15+
protected config: IBaseHandlerConfig;
16+
17+
constructor(handlerConfig: IBaseHandlerConfig) {
18+
this.config = handlerConfig;
19+
}
20+
21+
/**
22+
* Pure abstract method for getting Source Provider.
23+
* Source Provider is an object that contains info about from where we will download the artifact.
24+
* @access protected
25+
* @returns {IArtifactProvider} Objects that implement the IArtifactProvider interface
26+
*/
27+
protected abstract getSourceProvider(): IArtifactProvider;
28+
29+
/**
30+
* Pure abstract method for getting Destination Provider.
31+
* Destination Provider is an object that contains info about where we will download artifacts.
32+
* @access protected
33+
* @returns {IArtifactProvider} Objects that implement the IArtifactProvider interface
34+
*/
35+
protected abstract getDestinationProvider(): IArtifactProvider;
36+
37+
/**
38+
* Method to download Build Artifact.
39+
* Since the logic for downloading builds artifacts is the same for all
40+
* types of source and destination providers, we will implement this logic in the base class.
41+
* @access public
42+
* @returns {Promise<Array<ArtifactDownloadTicket>>} an array of Download Tickets
43+
*/
44+
public async downloadResources(): Promise<Array<ArtifactDownloadTicket>> {
45+
const downloader: ArtifactEngine = new ArtifactEngine();
46+
const sourceProvider: IArtifactProvider = this.getSourceProvider();
47+
const destinationProvider: IArtifactProvider = this.getDestinationProvider();
48+
49+
const downloadPromise: Promise<Array<ArtifactDownloadTicket>> = new Promise<Array<ArtifactDownloadTicket>>(async (downloadComplete, downloadFailed) => {
50+
try {
51+
// First attempt to download artifact
52+
const downloadTickets: Array<ArtifactDownloadTicket> = await downloader.processItems(sourceProvider, destinationProvider, this.config.downloaderOptions);
53+
54+
// We will proceed with the files check only if the "Check download files" option enabled
55+
if (this.config.checkDownloadedFiles && Array.isArray(downloadTickets)) {
56+
try {
57+
// Launch the files check, if all files are fully downloaded no exceptions will be thrown.
58+
handlerCheckDownloadedFiles(downloadTickets);
59+
downloadComplete(downloadTickets);
60+
} catch (error) {
61+
downloadFailed(error);
62+
}
63+
} else {
64+
downloadComplete(downloadTickets);
65+
}
66+
} catch (error) {
67+
downloadFailed(error);
68+
}
69+
});
70+
71+
return downloadPromise;
72+
}
73+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { DownloadHandler } from './DownloadHandler';
2+
import { IContainerHandlerConfig } from './HandlerConfigs';
3+
import { WebProvider, FilesystemProvider } from 'artifact-engine/Providers';
4+
import * as tl from 'azure-pipelines-task-lib/task';
5+
6+
/**
7+
* Handler for download artifact from related container resource.
8+
* Build Artifact will be downloaded via `_apis/resources/Containers/` resource.
9+
* @extends DownloadHandler
10+
* @example
11+
* const config: IContainerHandlerConfig = {...};
12+
* const downloadHandler: IContainerHandlerConfig = new IContainerHandlerConfig(config);
13+
* downloadHandler.downloadResources();
14+
*/
15+
export class DownloadHandlerContainer extends DownloadHandler {
16+
protected config: IContainerHandlerConfig;
17+
18+
constructor(handlerConfig: IContainerHandlerConfig) {
19+
super(handlerConfig);
20+
}
21+
22+
/**
23+
* To download artifact from container resource we will use `WebProvider` as source provider
24+
* @access protected
25+
* @returns {WebProvider} Configured Web Provider
26+
*/
27+
protected getSourceProvider(): WebProvider {
28+
console.log(tl.loc('DownloadingContainerResource', this.config.artifactInfo.resource.data));
29+
const containerParts: Array<string> = this.config.artifactInfo.resource.data.split('/');
30+
31+
if (containerParts.length < 3) {
32+
throw new Error(tl.loc('FileContainerInvalidArtifactData'));
33+
}
34+
35+
const containerId: number = parseInt(containerParts[1]);
36+
let containerPath: string = containerParts.slice(2, containerParts.length).join('/');
37+
38+
if (containerPath === '/') {
39+
//container REST api oddity. Passing '/' as itemPath downloads the first file instead of returning the meta data about the all the files in the root level.
40+
//This happens only if the first item is a file.
41+
containerPath = '';
42+
}
43+
44+
const variables = {};
45+
const itemsUrl: string = `${this.config.endpointUrl}/_apis/resources/Containers/${containerId}?itemPath=${encodeURIComponent(containerPath)}&isShallow=true&api-version=4.1-preview.4`;
46+
47+
console.log(tl.loc('DownloadArtifacts', this.config.artifactInfo.name, itemsUrl));
48+
49+
const provider: WebProvider = new WebProvider(itemsUrl, this.config.templatePath, variables, this.config.handler);
50+
return provider;
51+
}
52+
53+
/**
54+
* Since we download artifact to local storage we will use a `FilesystemProvider` as destination provider
55+
* @access protected
56+
* @returns {FilesystemProvider} Configured Filesystem Provider
57+
*/
58+
protected getDestinationProvider(): FilesystemProvider {
59+
const provider: FilesystemProvider = new FilesystemProvider(this.config.downloadPath);
60+
return provider;
61+
}
62+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { DownloadHandler } from './DownloadHandler';
2+
import { IContainerHandlerZipConfig } from './HandlerConfigs';
3+
import { FilesystemProvider, ZipProvider } from 'artifact-engine/Providers';
4+
import * as tl from 'azure-pipelines-task-lib/task';
5+
import * as path from 'path';
6+
import * as DecompressZip from 'decompress-zip';
7+
8+
/**
9+
* Handler for download artifact via build API
10+
* Build Artifact will be downloaded as zip archive via `/_apis/build/builds/` resource.
11+
* This handler was designed to work only on windows system.
12+
* @extends DownloadHandler
13+
* @example
14+
* const config: IContainerHandlerZipConfig = {...};
15+
* const downloadHandler: DownloadHandlerContainerZip = new DownloadHandlerContainerZip(config);
16+
* downloadHandler.downloadResources();
17+
*/
18+
export class DownloadHandlerContainerZip extends DownloadHandler {
19+
protected config: IContainerHandlerZipConfig;
20+
private readonly archiveUrl: string;
21+
private readonly zipLocation: string;
22+
23+
constructor(handlerConfig: IContainerHandlerZipConfig) {
24+
super(handlerConfig);
25+
this.archiveUrl = `${this.config.endpointUrl}/${this.config.projectId}/_apis/build/builds/${this.config.buildId}/artifacts?artifactName=${encodeURIComponent(this.config.artifactInfo.name)}&$format=zip`;
26+
this.zipLocation = path.join(this.config.downloadPath, `${this.config.artifactInfo.name}.zip`);
27+
}
28+
29+
/**
30+
* Unpack an archive with an artifact
31+
* @param unzipLocation path to the target artifact
32+
* @access private
33+
* @returns {Promise<void>} promise that will be resolved once the archive will be unpacked
34+
*/
35+
private unzipContainer(unzipLocation: string): Promise<void> {
36+
const unZipPromise: Promise<void> = new Promise<void>((resolve, reject) => {
37+
if (!tl.exist(this.zipLocation)) {
38+
return resolve();
39+
}
40+
41+
tl.debug(`Extracting ${this.zipLocation} to ${unzipLocation}`);
42+
43+
const unzipper = new DecompressZip(this.zipLocation);
44+
45+
unzipper.on('error', err => {
46+
return reject(tl.loc('ExtractionFailed', err));
47+
});
48+
49+
unzipper.on('extract', log => {
50+
tl.debug(`Extracted ${this.zipLocation} to ${unzipLocation} successfully`);
51+
return resolve();
52+
});
53+
54+
unzipper.extract({
55+
path: unzipLocation
56+
});
57+
58+
});
59+
60+
return unZipPromise;
61+
}
62+
63+
/**
64+
* Get zip provider.
65+
* Since we will download archived artifact we will use `ZipProvider` as source provider.
66+
* @access protected
67+
* @returns {ZipProvider} Configured Zip Provider
68+
*/
69+
protected getSourceProvider(): ZipProvider {
70+
console.log(tl.loc('DownloadArtifacts', this.config.artifactInfo.name, this.archiveUrl));
71+
const provider: ZipProvider = new ZipProvider(this.archiveUrl, this.config.handler);
72+
return provider;
73+
}
74+
75+
/**
76+
* Get filesystem provider.
77+
* Since we download artifact to local storage we will use a `FilesystemProvider` as destination provider.
78+
* @access protected
79+
* @returns {FilesystemProvider} Configured Filesystem Provider
80+
*/
81+
protected getDestinationProvider(): FilesystemProvider {
82+
const provider: FilesystemProvider = new FilesystemProvider(this.zipLocation);
83+
return provider;
84+
}
85+
86+
/**
87+
* Download and unpack an archive with an artifact.
88+
* @access public
89+
*/
90+
public downloadResources(): Promise<any> {
91+
const downloadProcess: Promise<any> = new Promise((resolve, reject) => {
92+
tl.debug('Starting downloadZip action');
93+
94+
if (tl.exist(this.zipLocation)) {
95+
tl.rmRF(this.zipLocation);
96+
}
97+
98+
super.downloadResources().then(() => {
99+
tl.debug(`Successfully downloaded from ${this.archiveUrl}`);
100+
101+
this.unzipContainer(this.config.downloadPath).then(() => {
102+
tl.debug(`Successfully extracted ${this.zipLocation}`);
103+
104+
if (tl.exist(this.zipLocation)) {
105+
tl.rmRF(this.zipLocation);
106+
}
107+
108+
resolve();
109+
110+
}).catch((error) => {
111+
reject(error);
112+
});
113+
114+
}).catch((error) => {
115+
reject(error);
116+
});
117+
});
118+
119+
return downloadProcess;
120+
}
121+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { DownloadHandler } from './DownloadHandler';
2+
import { FilesystemProvider } from 'artifact-engine/Providers';
3+
import * as tl from 'azure-pipelines-task-lib/task';
4+
import * as fs from 'fs';
5+
import * as path from 'path';
6+
7+
/**
8+
* Handler for download artifact via local file share
9+
* @extends DownloadHandler
10+
* @example
11+
* const config: IBaseHandlerConfig = {...};
12+
* const downloadHandler: DownloadHandlerFilePath = new DownloadHandlerFilePath(config);
13+
* downloadHandler.downloadResources();
14+
*/
15+
export class DownloadHandlerFilePath extends DownloadHandler {
16+
/**
17+
* Get source provider with source folder.
18+
* Since we will work with local files we use `Filesystem Provider` as source provider.
19+
* @access protected
20+
* @returns {FilesystemProvider} Configured Filesystem Provider
21+
*/
22+
protected getSourceProvider(): FilesystemProvider {
23+
const downloadUrl = this.config.artifactInfo.resource.data;
24+
const artifactName = this.config.artifactInfo.name.replace('/', '\\');
25+
let artifactLocation = path.join(downloadUrl, artifactName);
26+
27+
console.log(tl.loc('DownloadArtifacts', artifactName, artifactLocation));
28+
29+
if (!fs.existsSync(artifactLocation)) {
30+
console.log(tl.loc('ArtifactNameDirectoryNotFound', artifactLocation, downloadUrl));
31+
artifactLocation = downloadUrl;
32+
}
33+
34+
const provider: FilesystemProvider = new FilesystemProvider(artifactLocation, artifactName);
35+
return provider;
36+
}
37+
38+
/**
39+
* Get destination provider with destination folder.
40+
* Since we will work with local files we use `Filesystem Provider` as source provider.
41+
* @access protected
42+
* @returns {FilesystemProvider} Configured Filesystem Provider
43+
*/
44+
protected getDestinationProvider(): FilesystemProvider {
45+
const provider: FilesystemProvider = new FilesystemProvider(this.config.downloadPath);
46+
return provider;
47+
}
48+
}

0 commit comments

Comments
 (0)