Skip to content

Instantly share code, notes, and snippets.

@bmcdavid
Created April 17, 2018 16:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bmcdavid/6aa5f889ed65a0b0d3c4ff3faf2276ec to your computer and use it in GitHub Desktop.
Save bmcdavid/6aa5f889ed65a0b0d3c4ff3faf2276ec to your computer and use it in GitHub Desktop.
import path = require('path');
import os = require('os');
import tl = require('vsts-task-lib/task');
tl.setResourcePath(path.join(__dirname, 'task.json'));
(() => {
let variableDelimiter: string = tl.getInput('VariableDelimiter', false);
variableDelimiter = variableDelimiter === null ? ";" : variableDelimiter;
// contents is a multiline input containing glob patterns
let patterns: string[] = tl.getDelimitedInput('Contents', '\n', false);
let variablePatterns: string[] = tl.getDelimitedInput('VariableContents', variableDelimiter, false);
// add variables
variablePatterns.forEach(function (x) {
if (x) {
patterns.push(x);
}
});
let sourceFolder: string = tl.getPathInput('SourceFolder', true, false);
// Input that is used for backward compatibility with pre-sprint 95 symbol store artifacts.
// Pre-95 symbol store artifacts were simply file path artifacts, so we need to make sure
// not to delete the artifact share if it's a symbol store.
let buildCleanup: boolean = tl.getBoolInput('BuildCleanup');
// trim whitespace and root each pattern
patterns = patterns
.map((pattern: string) => pattern.trim())
.filter((pattern: string) => pattern != '')
.map((pattern: string) => path.join(sourceFolder, pattern));
tl.debug(`patterns: ${patterns}`);
tl._writeLine(`patterns: ${patterns}`);
// short-circuit if no patterns
if (!patterns.length) {
tl.debug('no patterns specified');
return;
}
// find all files
let foundPaths = tl.find(sourceFolder);
// short-circuit if not exists
if (!foundPaths.length) {
tl.debug('source folder not found. nothing to delete.');
tl.setResult(tl.TaskResult.Succeeded, tl.loc("NoFiles"));
return;
}
// Don't delete symbol store shares if this is a cleanup job for file-path artifacts.
//
// This check needs to be made based on the result of tl.find(). Otherwise intermittent network
// issues could result in a false assertion that the share is not a symbol store share.
//
// Opted to check each item name rather than the full path. Although it would suffice to check
// for 000Admin at the root of the share, it is difficult to accurately make a determination
// based on the full path. The problem is that the input share path would need to be run through
// a normalization function that could be trusted 100% to match the format produced by tl.find().
// For example if the input contains "\\\share", it would need to be normalized as "\\share". To
// avoid worrying about catching every normalization edge case, checking the item name suffices instead.
if (buildCleanup &&
foundPaths.some((itemPath: string) => path.basename(itemPath).toLowerCase() === '000admin')) {
tl.warning(tl.loc('SkippingSymbolStore', sourceFolder));
return;
}
// minimatch options
let matchOptions = { matchBase: true };
if (os.type().match(/^Win/)) {
matchOptions["nocase"] = true;
}
// apply the match patterns
let matches: string[] = tl.match(foundPaths, patterns, matchOptions);
// sort by length (descending) so files are deleted before folders
matches = matches.sort((a: string, b: string) => {
if (a.length == b.length) {
return 0;
}
return a.length > b.length ? -1 : 1;
});
// try to delete all files/folders, even if one errs
let errorHappened: boolean = false;
for (let itemPath of matches) {
try {
tl.rmRF(itemPath);
}
catch (err) {
tl.error(err);
errorHappened = true;
}
}
if (errorHappened) {
tl.setResult(tl.TaskResult.Failed, tl.loc("CantDeleteFiles"));
}
})();
{
"id": "7d08a160-6f10-4815-b8ae-86e4817e5d57",
"name": "CustomDeleteFiles",
"friendlyName": "Custom Delete Files",
"description": "Delete files or folders. (The minimatch patterns will only match file paths, not folder paths)",
"helpMarkDown": "[More Information](https://go.microsoft.com/fwlink/?LinkID=722333)",
"category": "Utility",
"visibility": [
"Build"
],
"runsOn": [
"Agent",
"DeploymentGroup"
],
"author": "Brad",
"version": {
"Major": 1,
"Minor": 0,
"Patch": 4
},
"demands": [],
"minimumAgentVersion": "1.92.0",
"inputs": [
{
"name": "SourceFolder",
"type": "filePath",
"label": "Source Folder",
"defaultValue": "",
"required": false,
"helpMarkDown": "The source folder that the deletion(s) will be run from. Empty is the root of the repo. Use [variables](https://go.microsoft.com/fwlink/?LinkID=550988) if files are not in the repo. Example: $(agent.builddirectory)"
},
{
"name": "Contents",
"type": "multiLine",
"label": "Contents",
"defaultValue": "myFileShare",
"required": false,
"helpMarkDown": "File/folder paths to delete. Supports multiple lines of minimatch patterns. [More Information](https://go.microsoft.com/fwlink/?LinkID=722333)"
},
{
"name": "VariableContents",
"type": "string",
"label": "Variable Contents",
"defaultValue": "",
"required": false,
"helpMarkDown": "Allows for variables to be read in"
},
{
"name": "VariableDelimiter",
"type": "string",
"label": "Variable Delimiter",
"defaultValue": ";",
"required": false,
"helpMarkDown": "Splits variable on the delimiter"
}
],
"instanceNameFormat": "Delete files from $(SourceFolder)",
"execution": {
"Node": {
"target": "deletefiles.js",
"argumentFormat": ""
}
},
"messages": {
"CantDeleteFiles": "Couldn't delete one or more files",
"SkippingSymbolStore": "Skipping delete for symbol store file share: %s",
"NoFiles": "No files to delete."
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment