Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect NuGet version and adjust behavior by per-version quirks #2336

Merged
merged 6 commits into from
Aug 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Tasks/Common/nuget-task-common/INuGetCommandOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {NuGetEnvironmentSettings} from "./NuGetToolRunner"

export interface INuGetCommandOptions {
/** settings used to initialize the environment NuGet.exe is invoked in */
environment: NuGetEnvironmentSettings,
/** full path to NuGet.exe */
nuGetPath: string,
/** path to the NuGet config file. Passed as the -ConfigFile argument. */
configFile: string
}

export default INuGetCommandOptions;
6 changes: 3 additions & 3 deletions Tasks/Common/nuget-task-common/NuGetConfigHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,19 @@ export class NuGetConfigHelper {
}
}

if(!configXml.configuration.packageSources || !configXml.configuration.packageSources.add) {
if (!configXml.configuration.packageSources || !configXml.configuration.packageSources.add) {
tl.warning(tl.loc("NGCommon_NoSourcesFoundInConfig", this._nugetConfigPath))
return [];
}

for (var i = 0; i < configXml.configuration.packageSources.add.count(); i++) {
sourceKey = configXml.configuration.packageSources.add.at(i).attributes().key;
sourceValue = configXml.configuration.packageSources.add.at(i).attributes().value;
if(!sourceKey || !sourceValue) {
if (!sourceKey || !sourceValue) {
continue;
}

packageSource = {feedName: sourceKey, feedUri: sourceValue};
packageSource = { feedName: sourceKey, feedUri: sourceValue };

// check if need to add credential to feed
if (this.shouldGetCredentialsForFeed(packageSource)) {
Expand Down
132 changes: 132 additions & 0 deletions Tasks/Common/nuget-task-common/NuGetQuirks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import VersionInfoVersion from './pe-parser/VersionInfoVersion'

export enum NuGetQuirkName {
/** Race condition in credential provider which causes NuGet to not supply credentials */
CredentialProviderRace,

/** No credential provider support */
NoCredentialProvider,

/** repositoryPath value in nuget.config is relative to the wrong nuget.config in some cases */
RelativeRepositoryPathBug,

/** does not send NTLM credentials on follow-up requests */
NtlmReAuthBug,

/** Does not support authentication to TFS on-premises via credential provider */
NoTfsOnPremAuthCredentialProvider,

/** Does not support authentication to TFS on-premises via nuget.config */
NoTfsOnPremAuthConfig,

/** Does not support the NuGet v3 protocol */
NoV3,
}

interface VersionRange {
begin: VersionInfoVersion;
beginIsInclusive: boolean;
end: VersionInfoVersion;
endIsInclusive: boolean;
}

function halfOpenRange(begin: VersionInfoVersion, end: VersionInfoVersion): VersionRange {
return { begin, beginIsInclusive: true, end, endIsInclusive: false };
}

function closedRange(begin: VersionInfoVersion, end: VersionInfoVersion): VersionRange {
return { begin, beginIsInclusive: true, end, endIsInclusive: true };
}

function versionIsInRange(version: VersionInfoVersion, range: VersionRange): boolean {
const beginComparison = VersionInfoVersion.compare(version, range.begin);
const endComparison = VersionInfoVersion.compare(version, range.end);

const beginResult = range.beginIsInclusive ? beginComparison >= 0 : beginComparison > 0;
const endResult = range.endIsInclusive ? endComparison <= 0 : endComparison < 0;

return beginResult && endResult;
}

interface QuirkDescriptor {
quirk: NuGetQuirkName;
versionRanges: VersionRange[]
}

const nuget300 = new VersionInfoVersion(3, 0, 0, 0);
const nuget320 = new VersionInfoVersion(3, 2, 0, 0);
const nuget330 = new VersionInfoVersion(3, 3, 0, 0);
const nuget340 = new VersionInfoVersion(3, 4, 0, 0);
const nuget350 = new VersionInfoVersion(3, 5, 0, 0);
const nuget350_1707 = new VersionInfoVersion(3, 5, 0, 1707)
const nuget351 = new VersionInfoVersion(3, 5, 1, 0);
const nuget351_1707 = new VersionInfoVersion(3, 5, 1, 1707)

const allQuirks: QuirkDescriptor[] = [
{
quirk: NuGetQuirkName.CredentialProviderRace,
// 1707 is the build of * 3.5.1 * where we first saw the bug resolved,
// I'm not sure which build of 3.5.0 is the first to not have the bug,
// but it would be less than 1707
versionRanges: [
halfOpenRange(nuget320, nuget350_1707),
halfOpenRange(nuget351, nuget351_1707)]
},
{
quirk: NuGetQuirkName.NoCredentialProvider,
versionRanges: [halfOpenRange(VersionInfoVersion.MIN_VERSION, nuget320)]
},
{
quirk: NuGetQuirkName.RelativeRepositoryPathBug,
versionRanges: [halfOpenRange(nuget330, nuget340)]
},
{
quirk: NuGetQuirkName.NtlmReAuthBug,
versionRanges: [halfOpenRange(nuget330, nuget340)]
},
{
quirk: NuGetQuirkName.NoV3,
versionRanges: [halfOpenRange(VersionInfoVersion.MIN_VERSION, nuget300)]
},
{
quirk: NuGetQuirkName.NoTfsOnPremAuthConfig,
versionRanges: [closedRange(VersionInfoVersion.MIN_VERSION, VersionInfoVersion.MAX_VERSION)]
},
{
quirk: NuGetQuirkName.NoTfsOnPremAuthCredentialProvider,
versionRanges: [halfOpenRange(VersionInfoVersion.MIN_VERSION, nuget351)]
}
]

/** default quirks to use if the nuget version can't be determined */
export var defaultQuirks = [
NuGetQuirkName.NoCredentialProvider,
NuGetQuirkName.NoV3,
NuGetQuirkName.NoTfsOnPremAuthConfig,
NuGetQuirkName.NoTfsOnPremAuthCredentialProvider,
];

function resolveQuirks(nuGetVersion: VersionInfoVersion, definitions: QuirkDescriptor[]): NuGetQuirkName[] {
return definitions
.filter(quirkDesc => quirkDesc.versionRanges.some(range => versionIsInRange(nuGetVersion, range)))
.map(quirkDesc => quirkDesc.quirk);
}

export class NuGetQuirks {
constructor(public nuGetVersion: VersionInfoVersion, public quirks: NuGetQuirkName[]) { }

public static fromVersion(nuGetVersion: VersionInfoVersion, definitions?: QuirkDescriptor[]) {
definitions = definitions || allQuirks;
return new NuGetQuirks(nuGetVersion, resolveQuirks(nuGetVersion, definitions));
}

public hasQuirk(quirk: NuGetQuirkName): boolean {
return this.quirks.some(x => x === quirk);
}

public getQuirkNames(): string[] {
return this.quirks.map(x => NuGetQuirkName[x]);
}
}

export default NuGetQuirks;
88 changes: 64 additions & 24 deletions Tasks/Common/nuget-task-common/NuGetToolRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

import * as tl from 'vsts-task-lib/task';
import {ToolRunner, IExecOptions, IExecResult} from 'vsts-task-lib/toolrunner';
import * as auth from "./Authentication"
import * as os from 'os';
import * as path from 'path';
import * as url from 'url';

import * as auth from "./Authentication"
import * as ngutil from "./Utility";
import {defaultQuirks, NuGetQuirks, NuGetQuirkName} from "./NuGetQuirks"
import * as peParser from "./pe-parser"

interface EnvironmentDictionary { [key: string]: string }

Expand Down Expand Up @@ -112,8 +114,7 @@ function locateTool(tool: string, opts?: LocateOptions) {

tl.debug(`looking for tool ${tool}`)

for (let thisVariant of opts.toolFilenames)
{
for (let thisVariant of opts.toolFilenames) {
tl.debug(`looking for tool variant ${thisVariant}`);

for (let possibleLocation of searchPath) {
Expand Down Expand Up @@ -155,20 +156,53 @@ export function locateNuGetExe(userNuGetExePath: string): string {
toolFilenames: ['nuget.exe', 'NuGet.exe', 'nuget', 'NuGet']
});


if (!toolPath) {
throw new Error(tl.loc("NGCommon_UnableToFindTool", 'NuGet'));
}

return toolPath;
}

function isHosted(): boolean {
export async function getNuGetQuirksAsync(nuGetExePath: string): Promise<NuGetQuirks> {
try {
const version = await peParser.getFileVersionInfoAsync(nuGetExePath);
const quirks = NuGetQuirks.fromVersion(version.fileVersion);

tl._writeLine(tl.loc("NGCommon_DetectedNuGetVersion", version.fileVersion, version.strings.ProductVersion))
tl.debug(`Quirks for ${version.fileVersion}:`);
quirks.getQuirkNames().forEach(quirk => {
tl.debug(` ${quirk}`)
});

return quirks;
} catch (err) {
if (err.code && (
err.code === "invalidSignature"
|| err.code === "noResourceSection"
|| err.code === "noVersionResource")) {

tl.debug("Cannot read version from NuGet. Using default quirks:")
defaultQuirks.forEach(quirk => {
tl.debug(` ${NuGetQuirkName[quirk]}`)
});
return new NuGetQuirks(null, defaultQuirks);
}

throw err;
}
}

function isOnPremisesTfs(): boolean {
if(tl.getVariable("NuGetTasks.IsHostedTestEnvironment") === "true") {
return false;
}

// not an ideal way to detect hosted, but there isn't a variable for it, and we can't make network calls from here
// due to proxy issues.
const collectionUri = tl.getVariable("System.TeamFoundationCollectionUri");
const parsedCollectionUri = url.parse(collectionUri);
return /\.visualstudio\.com$/i.test(parsedCollectionUri.hostname);
return !(/\.visualstudio\.com$/i.test(parsedCollectionUri.hostname));
}

// Currently, there is a race condition of some sort that causes nuget to not send credentials sometimes
Expand All @@ -178,7 +212,7 @@ function isHosted(): boolean {
// Therefore, we are enabling credential provider on on-premises and disabling it on hosted. We allow for test
// instances by an override variable.

export function isCredentialProviderEnabled(): boolean {
export function isCredentialProviderEnabled(quirks: NuGetQuirks): boolean {
// set NuGet.ForceEnableCredentialProvider to "true" to force allowing the credential provider flow, "false"
// to force *not* allowing the credential provider flow, or unset/anything else to fall through to the
// hosted environment detection logic
Expand All @@ -192,18 +226,24 @@ export function isCredentialProviderEnabled(): boolean {
tl.debug("Credential provider is force-disabled for testing purposes.");
return false;
}

if (isHosted()) {
tl.debug("Credential provider is disabled on hosted.");

if (quirks.hasQuirk(NuGetQuirkName.NoCredentialProvider)
|| quirks.hasQuirk(NuGetQuirkName.CredentialProviderRace)) {
tl.debug("Credential provider is disabled due to quirks.");
return false;
}
else {
tl.debug("Credential provider is enabled.")
return true;

if (isOnPremisesTfs() && (
quirks.hasQuirk(NuGetQuirkName.NoTfsOnPremAuthCredentialProvider))) {
tl.debug("Credential provider is disabled due to on-prem quirks.")
return false;
}

tl.debug("Credential provider is enabled.");
return true;
}

export function isCredentialConfigEnabled(): boolean {
export function isCredentialConfigEnabled(quirks: NuGetQuirks): boolean {
// set NuGet.ForceEnableCredentialConfig to "true" to force allowing config-based credential flow, "false"
// to force *not* allowing config-based credential flow, or unset/anything else to fall through to the
// hosted environment detection logic
Expand All @@ -217,25 +257,25 @@ export function isCredentialConfigEnabled(): boolean {
tl.debug("Credential config is force-disabled for testing purposes.");
return false;
}

// credentials in config will always fail for on-prem
if (!isHosted()) {
tl.debug("Credential config is disabled on on-premises TFS.");


if (isOnPremisesTfs() && (
quirks.hasQuirk(NuGetQuirkName.NoTfsOnPremAuthConfig))) {
tl.debug("Credential config is disabled due to on-prem quirks.")
return false;
}
else {
tl.debug("Credential config is enabled.")
return true;
}

tl.debug("Credential config is enabled.")
return true;
}

export function locateCredentialProvider(): string {
const credentialProviderLocation = locateTool('CredentialProvider.TeamBuild.exe');
if(!credentialProviderLocation) {
if (!credentialProviderLocation) {
tl.debug("Credential provider is not present.");
return null;
}

return isCredentialProviderEnabled() ? credentialProviderLocation : null;
return credentialProviderLocation;
}

10 changes: 4 additions & 6 deletions Tasks/Common/nuget-task-common/Utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function resolveFilterSpec(filterSpec: string, basePath?: string, allowEm

export function resolveWildcardPath(pattern: string, allowEmptyWildcardMatch?: boolean): string[] {
let isWindows = os.platform() === 'win32';

// Resolve files for the specified value or pattern
var filesList: string[];

Expand All @@ -56,7 +56,7 @@ export function resolveWildcardPath(pattern: string, allowEmptyWildcardMatch?: b
filesList = [];
}
else if (pattern.indexOf('*') == -1 && pattern.indexOf('?') == -1) {

// No pattern found, check literal path to a single file
tl.checkPath(pattern, 'files');

Expand Down Expand Up @@ -113,12 +113,10 @@ export function resolveWildcardPath(pattern: string, allowEmptyWildcardMatch?: b
}
}

if (!isWindows)
{
if (!isWindows) {
return filesList;
}
else
{
else {
return filesList.map(file => file.split("/").join("\\"));
}
}
Expand Down
9 changes: 9 additions & 0 deletions Tasks/Common/nuget-task-common/pe-parser/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# ignores

_temp/
*.js
*.js.map
*.d.ts
*.log
typings/
node_modules/
6 changes: 6 additions & 0 deletions Tasks/Common/nuget-task-common/pe-parser/IReadableFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface IReadableFile {
readAsync(buffer: Buffer, offset: number, length: number, position: number): Promise<number>;
closeAsync(): Promise<void>;
}

export default IReadableFile;
Loading