Created
April 17, 2018 16:02
-
-
Save bmcdavid/6aa5f889ed65a0b0d3c4ff3faf2276ec to your computer and use it in GitHub Desktop.
Custom VSTS delete task, based on https://github.com/Microsoft/vsts-tasks/tree/master/Tasks/DeleteFiles.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")); | |
} | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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