Skip to content

Commit

Permalink
Creating the all-in-one NuGet task
Browse files Browse the repository at this point in the history
  • Loading branch information
emanuelquintero committed May 26, 2017
1 parent b72e3fa commit dafc900
Show file tree
Hide file tree
Showing 65 changed files with 3,317 additions and 668 deletions.
83 changes: 83 additions & 0 deletions Tasks/NuGetCommand/Common/Authentication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import * as tl from "vsts-task-lib/task";


export interface IPackageSource {
feedName: string;
feedUri: string;
isInternal: boolean;
}

export class NuGetAuthInfo {
constructor(
public internalAuthInfo: InternalAuthInfo,
public externalAuthInfo?: ExternalAuthInfo[]) {
}
}

export class InternalAuthInfo
{
constructor(
public uriPrefixes: string[],
public accessToken: string,
public useCredProvider: string,
public useCredConfig: boolean) {
}
}

export class ExternalAuthInfo
{
constructor(
public packageSource: IPackageSource,
public authType: ExternalAuthType) {
}
}

export class TokenExternalAuthInfo extends ExternalAuthInfo
{
constructor(
public packageSource: IPackageSource,
public token: string)
{
super(packageSource, ExternalAuthType.Token);
}
}

export class UsernamePasswordExternalAuthInfo extends ExternalAuthInfo
{
constructor(
public packageSource: IPackageSource,
public username: string,
public password: string)
{
super(packageSource, ExternalAuthType.UsernamePassword);
}
}

export class ApiKeyExternalAuthInfo extends ExternalAuthInfo
{
constructor(
public packageSource: IPackageSource,
public apiKey: string)
{
super(packageSource, ExternalAuthType.ApiKey);
}
}

export enum ExternalAuthType
{
Token,
UsernamePassword,
ApiKey
}

export function getSystemAccessToken(): string {
tl.debug("Getting credentials for local feeds");
let auth = tl.getEndpointAuthorization("SYSTEMVSSCONNECTION", false);
if (auth.scheme === "OAuth") {
tl.debug("Got auth token");
return auth.parameters["AccessToken"];
}
else {
tl.warning("Could not determine credentials to use for NuGet");
}
}
12 changes: 12 additions & 0 deletions Tasks/NuGetCommand/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;
264 changes: 264 additions & 0 deletions Tasks/NuGetCommand/Common/NuGetConfigHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import * as fs from "fs";
import * as path from "path";
import * as url from "url";
import * as Q from "q";
import * as tl from "vsts-task-lib/task";

import * as auth from "./Authentication";
import { IPackageSource } from "./Authentication";
import * as ngToolRunner from "./NuGetToolRunner";

let xmlreader = require("xmlreader");


export class NuGetConfigHelper {
private tempNugetConfigBaseDir
= tl.getVariable("Agent.BuildDirectory")
|| tl.getVariable("Agent.ReleaseDirectory")
|| process.cwd();
private tempNugetConfigDir = path.join(this.tempNugetConfigBaseDir, "Nuget");
private tempNugetConfigFileName = "tempNuGet_" + tl.getVariable("build.buildId") + ".config";
public tempNugetConfigPath = undefined;

constructor(
private nugetPath: string,
private nugetConfigPath: string,
private authInfo: auth.NuGetAuthInfo,
private environmentSettings: ngToolRunner.NuGetEnvironmentSettings)
{
}

public ensureTempConfigCreated() {
// save nuget config file to agent build directory
console.log("save nuget.config to temp config file");
if (!(fs.existsSync(this.tempNugetConfigDir))) {
fs.mkdirSync(this.tempNugetConfigDir);
}

this.tempNugetConfigPath = path.join(this.tempNugetConfigDir, this.tempNugetConfigFileName);

if (!fs.existsSync(this.tempNugetConfigPath))
{
if (this.nugetConfigPath) {
// don't use cp as that copies the read-only flag, and tfvc sets that on files
let content = fs.readFileSync(this.nugetConfigPath);
fs.writeFileSync(this.tempNugetConfigPath, content);
}
else {
// small file, use writeFileSync
fs.writeFileSync(this.tempNugetConfigPath, "<configuration/>");
}
}
}

public addSourcesToTempNuGetConfig(packageSources: IPackageSource[]): void
{
tl.debug('Adding sources to nuget.config');
this.ensureTempConfigCreated();
this.addSourcesToTempNugetConfigInternal(packageSources);
}

public async setAuthForSourcesInTempNuGetConfigAsync(): Promise<void>
{
tl.debug('Setting auth in the temp nuget.config');

let sources = await this.getSourcesFromTempNuGetConfig();
if (sources.length < 1)
{
tl.debug('Not setting up auth for temp nuget.config as there are no sources');
return;
}

this.ensureTempConfigCreated();
sources.forEach((source) => {
if (source.isInternal)
{
if(this.authInfo.internalAuthInfo.useCredConfig)
{
tl.debug('Setting auth for internal source ' + source.feedUri);
// Removing source first
this.removeSourceFromTempNugetConfig(source);
// Re-adding source with creds
this.addSourceWithUsernamePasswordToTempNuGetConfig(source, "VssSessionToken", this.authInfo.internalAuthInfo.accessToken);
}
}
// Source is external
else
{
if (!this.authInfo.externalAuthInfo || this.authInfo.externalAuthInfo.length < 1)
{
return;
}

let indexAuthInfo: number = this.authInfo.externalAuthInfo.findIndex(externalEndpoint => url.parse(externalEndpoint.packageSource.feedUri).href.toLowerCase() === url.parse(source.feedUri).href.toLowerCase());
if(indexAuthInfo > -1)
{
let externalEndpointAuthInfo: auth.ExternalAuthInfo = this.authInfo.externalAuthInfo[indexAuthInfo];
tl.debug('Setting auth for external source ' + source.feedUri);
console.log(tl.loc("Info_MatchingUrlWasFoundSettingAuth") + source.feedUri);
switch(externalEndpointAuthInfo.authType)
{
case (auth.ExternalAuthType.UsernamePassword):
let usernamePwdAuthInfo = externalEndpointAuthInfo as auth.UsernamePasswordExternalAuthInfo;
this.removeSourceFromTempNugetConfig(source);
this.addSourceWithUsernamePasswordToTempNuGetConfig(source, usernamePwdAuthInfo.username, usernamePwdAuthInfo.password);
break;
case (auth.ExternalAuthType.Token):
let tokenAuthInfo = externalEndpointAuthInfo as auth.TokenExternalAuthInfo;
this.removeSourceFromTempNugetConfig(source);
this.addSourceWithUsernamePasswordToTempNuGetConfig(source, "CustomToken", tokenAuthInfo.token);
break;
case (auth.ExternalAuthType.ApiKey):
let apiKeyAuthInfo = externalEndpointAuthInfo as auth.ApiKeyExternalAuthInfo;
this.setApiKeyForSourceInTempNuGetConfig(source, apiKeyAuthInfo.apiKey);
break;
default:
break;
}
}
}
});
}

private getSourcesFromTempNuGetConfig(): Q.Promise<IPackageSource[]> {
// load content of the user's nuget.config
let configPath: string = this.tempNugetConfigPath ? this.tempNugetConfigPath : this.nugetConfigPath;

if (!configPath)
{
return Q.resolve([]);
}

tl.debug('Getting sources from NuGet.config in this location: ' + configPath);

let xmlString = fs.readFileSync(configPath).toString();

// strip BOM; xml parser doesn't like it
if (xmlString.charCodeAt(0) === 0xFEFF) {
xmlString = xmlString.substr(1);
}

// get package sources
return Q.nfcall<any>(xmlreader.read, xmlString)
.then(configXml => {
let packageSources = [];
let packageSource: IPackageSource;
let sourceKey;
let sourceValue;

// give clearer errors if the user has set an invalid nuget.config
if (!configXml.configuration) {
if (configXml.packages) {
throw new Error(tl.loc(
"NGCommon_NuGetConfigIsPackagesConfig",
this.nugetConfigPath,
tl.getVariable("Task.DisplayName")));
}
else {
throw new Error(tl.loc("NGCommon_NuGetConfigIsInvalid", this.nugetConfigPath));
}
}

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

for (let 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) {
continue;
}

packageSource = { feedName: sourceKey, feedUri: sourceValue, isInternal: false };
let isInternalFeed: boolean = this.shouldGetCredentialsForFeed(packageSource);
packageSource.isInternal = isInternalFeed;
packageSources.push(packageSource);
}

return packageSources;
});
}

private removeSourceFromTempNugetConfig(packageSource: IPackageSource) {
let nugetTool = ngToolRunner.createNuGetToolRunner(this.nugetPath, this.environmentSettings, this.authInfo);

nugetTool.arg("sources");
nugetTool.arg("Remove");
nugetTool.arg("-NonInteractive");
nugetTool.arg("-Name");
nugetTool.arg(packageSource.feedName);
nugetTool.arg("-ConfigFile");
nugetTool.arg(this.tempNugetConfigPath);

// short run, use execSync
nugetTool.execSync();
}


private addSourcesToTempNugetConfigInternal(packageSources: IPackageSource[]) {
packageSources.forEach((source) => {
let nugetTool = ngToolRunner.createNuGetToolRunner(this.nugetPath, this.environmentSettings, this.authInfo);

nugetTool.arg("sources");
nugetTool.arg("Add");
nugetTool.arg("-NonInteractive");
nugetTool.arg("-Name");
nugetTool.arg(source.feedName);
nugetTool.arg("-Source");
nugetTool.arg(source.feedUri);
nugetTool.arg("-ConfigFile");
nugetTool.arg(this.tempNugetConfigPath);

// short run, use execSync
nugetTool.execSync();
});
}

private addSourceWithUsernamePasswordToTempNuGetConfig(source: IPackageSource, username: string, password: string)
{
let nugetTool = ngToolRunner.createNuGetToolRunner(this.nugetPath, this.environmentSettings, this.authInfo);
nugetTool.arg("sources");
nugetTool.arg("Add");
nugetTool.arg("-NonInteractive");
nugetTool.arg("-Name");
nugetTool.arg(source.feedName);
nugetTool.arg("-Source");
nugetTool.arg(source.feedUri);
nugetTool.arg("-ConfigFile");
nugetTool.arg(this.tempNugetConfigPath);
nugetTool.arg("-Username");
nugetTool.arg(username);
nugetTool.arg("-Password");
nugetTool.arg(password);

if (tl.osType() !== 'Windows_NT') {
// only Windows supports DPAPI. Older NuGets fail to add credentials at all if DPAPI fails.
nugetTool.arg("-StorePasswordInClearText");
}

// short run, use execSync
nugetTool.execSync();
}

private setApiKeyForSourceInTempNuGetConfig(source: IPackageSource, apiKey: string)
{
let nugetTool = ngToolRunner.createNuGetToolRunner(this.nugetPath, this.environmentSettings, this.authInfo);
nugetTool.arg("setapikey");
nugetTool.arg(apiKey);
nugetTool.arg("-NonInteractive");
nugetTool.arg("-Source");
nugetTool.arg(source.feedUri);
nugetTool.arg("-ConfigFile");
nugetTool.arg(this.tempNugetConfigPath);

// short run, use execSync
nugetTool.execSync();
}

private shouldGetCredentialsForFeed(source: IPackageSource): boolean {
let uppercaseUri = source.feedUri.toUpperCase();
return this.authInfo.internalAuthInfo.uriPrefixes.some(prefix => uppercaseUri.indexOf(prefix.toUpperCase()) === 0);
}
}
6 changes: 6 additions & 0 deletions Tasks/NuGetCommand/Common/NuGetPackUtilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Placed as a separate file for the purpose of unit testing

export function getUtcDateString(): string {
let now: Date = new Date();
return `${now.getFullYear()}${now.getUTCMonth()}${now.getUTCDate()}-${now.getUTCHours()}${now.getUTCMinutes()}${now.getUTCSeconds()}`;
}
Loading

0 comments on commit dafc900

Please sign in to comment.