Skip to content

Commit 56a2903

Browse files
Merge pull request #4397 from emanuelquintero/users/jahquin/nugetv2
Creating the all-in-one NuGet task
2 parents e542a20 + f51367f commit 56a2903

Some content is hidden

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

66 files changed

+3374
-688
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import * as tl from "vsts-task-lib/task";
2+
3+
4+
export interface IPackageSource {
5+
feedName: string;
6+
feedUri: string;
7+
isInternal: boolean;
8+
}
9+
10+
export class NuGetAuthInfo {
11+
constructor(
12+
public internalAuthInfo: InternalAuthInfo,
13+
public externalAuthInfo?: ExternalAuthInfo[]) {
14+
}
15+
}
16+
17+
export class InternalAuthInfo
18+
{
19+
constructor(
20+
public uriPrefixes: string[],
21+
public accessToken: string,
22+
public useCredProvider: string,
23+
public useCredConfig: boolean) {
24+
}
25+
}
26+
27+
export class ExternalAuthInfo
28+
{
29+
constructor(
30+
public packageSource: IPackageSource,
31+
public authType: ExternalAuthType) {
32+
}
33+
}
34+
35+
export class TokenExternalAuthInfo extends ExternalAuthInfo
36+
{
37+
constructor(
38+
public packageSource: IPackageSource,
39+
public token: string)
40+
{
41+
super(packageSource, ExternalAuthType.Token);
42+
}
43+
}
44+
45+
export class UsernamePasswordExternalAuthInfo extends ExternalAuthInfo
46+
{
47+
constructor(
48+
public packageSource: IPackageSource,
49+
public username: string,
50+
public password: string)
51+
{
52+
super(packageSource, ExternalAuthType.UsernamePassword);
53+
}
54+
}
55+
56+
export class ApiKeyExternalAuthInfo extends ExternalAuthInfo
57+
{
58+
constructor(
59+
public packageSource: IPackageSource,
60+
public apiKey: string)
61+
{
62+
super(packageSource, ExternalAuthType.ApiKey);
63+
}
64+
}
65+
66+
export enum ExternalAuthType
67+
{
68+
Token,
69+
UsernamePassword,
70+
ApiKey
71+
}
72+
73+
export function getSystemAccessToken(): string {
74+
tl.debug("Getting credentials for local feeds");
75+
let auth = tl.getEndpointAuthorization("SYSTEMVSSCONNECTION", false);
76+
if (auth.scheme === "OAuth") {
77+
tl.debug("Got auth token");
78+
return auth.parameters["AccessToken"];
79+
}
80+
else {
81+
tl.warning("Could not determine credentials to use for NuGet");
82+
}
83+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { NuGetEnvironmentSettings } from "./NuGetToolRunner";
2+
3+
export interface INuGetCommandOptions {
4+
/** settings used to initialize the environment NuGet.exe is invoked in */
5+
environment: NuGetEnvironmentSettings;
6+
/** full path to NuGet.exe */
7+
nuGetPath: string;
8+
/** path to the NuGet config file. Passed as the -ConfigFile argument. */
9+
configFile: string;
10+
}
11+
12+
export default INuGetCommandOptions;
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
import * as fs from "fs";
2+
import * as path from "path";
3+
import * as url from "url";
4+
import * as Q from "q";
5+
import * as tl from "vsts-task-lib/task";
6+
7+
import * as auth from "./Authentication";
8+
import { IPackageSource } from "./Authentication";
9+
import * as ngToolRunner from "./NuGetToolRunner";
10+
11+
let xmlreader = require("xmlreader");
12+
13+
14+
export class NuGetConfigHelper {
15+
private tempNugetConfigBaseDir
16+
= tl.getVariable("Agent.BuildDirectory")
17+
|| tl.getVariable("Agent.ReleaseDirectory")
18+
|| process.cwd();
19+
private tempNugetConfigDir = path.join(this.tempNugetConfigBaseDir, "Nuget");
20+
private tempNugetConfigFileName = "tempNuGet_" + tl.getVariable("build.buildId") + ".config";
21+
public tempNugetConfigPath = undefined;
22+
23+
constructor(
24+
private nugetPath: string,
25+
private nugetConfigPath: string,
26+
private authInfo: auth.NuGetAuthInfo,
27+
private environmentSettings: ngToolRunner.NuGetEnvironmentSettings)
28+
{
29+
}
30+
31+
public ensureTempConfigCreated() {
32+
// save nuget config file to agent build directory
33+
console.log(tl.loc("Info_SavingTempConfig"));
34+
35+
if (!tl.exist(this.tempNugetConfigDir)) {
36+
tl.mkdirP(this.tempNugetConfigDir);
37+
}
38+
39+
this.tempNugetConfigPath = path.join(this.tempNugetConfigDir, this.tempNugetConfigFileName);
40+
41+
if (!tl.exist(this.tempNugetConfigPath))
42+
{
43+
if (this.nugetConfigPath) {
44+
// don't use cp as that copies the read-only flag, and tfvc sets that on files
45+
let content = fs.readFileSync(this.nugetConfigPath);
46+
tl.writeFile(this.tempNugetConfigPath, content);
47+
}
48+
else {
49+
// small file, use writeFileSync
50+
tl.writeFile(this.tempNugetConfigPath, "<configuration/>");
51+
}
52+
}
53+
}
54+
55+
public addSourcesToTempNuGetConfig(packageSources: IPackageSource[]): void
56+
{
57+
tl.debug('Adding sources to nuget.config');
58+
this.ensureTempConfigCreated();
59+
this.addSourcesToTempNugetConfigInternal(packageSources);
60+
}
61+
62+
public async setAuthForSourcesInTempNuGetConfigAsync(): Promise<void>
63+
{
64+
tl.debug('Setting auth in the temp nuget.config');
65+
66+
let sources = await this.getSourcesFromTempNuGetConfig();
67+
if (sources.length < 1)
68+
{
69+
tl.debug('Not setting up auth for temp nuget.config as there are no sources');
70+
return;
71+
}
72+
73+
this.ensureTempConfigCreated();
74+
sources.forEach((source) => {
75+
if (source.isInternal)
76+
{
77+
if(this.authInfo.internalAuthInfo.useCredConfig)
78+
{
79+
tl.debug('Setting auth for internal source ' + source.feedUri);
80+
// Removing source first
81+
this.removeSourceFromTempNugetConfig(source);
82+
// Re-adding source with creds
83+
this.addSourceWithUsernamePasswordToTempNuGetConfig(source, "VssSessionToken", this.authInfo.internalAuthInfo.accessToken);
84+
}
85+
}
86+
// Source is external
87+
else
88+
{
89+
if (!this.authInfo.externalAuthInfo || this.authInfo.externalAuthInfo.length < 1)
90+
{
91+
return;
92+
}
93+
94+
let indexAuthInfo: number = this.authInfo.externalAuthInfo.findIndex(externalEndpoint => url.parse(externalEndpoint.packageSource.feedUri).href.toLowerCase() === url.parse(source.feedUri).href.toLowerCase());
95+
if(indexAuthInfo > -1)
96+
{
97+
let externalEndpointAuthInfo: auth.ExternalAuthInfo = this.authInfo.externalAuthInfo[indexAuthInfo];
98+
tl.debug('Setting auth for external source ' + source.feedUri);
99+
console.log(tl.loc("Info_MatchingUrlWasFoundSettingAuth") + source.feedUri);
100+
switch(externalEndpointAuthInfo.authType)
101+
{
102+
case (auth.ExternalAuthType.UsernamePassword):
103+
let usernamePwdAuthInfo = externalEndpointAuthInfo as auth.UsernamePasswordExternalAuthInfo;
104+
this.removeSourceFromTempNugetConfig(source);
105+
this.addSourceWithUsernamePasswordToTempNuGetConfig(source, usernamePwdAuthInfo.username, usernamePwdAuthInfo.password);
106+
break;
107+
case (auth.ExternalAuthType.Token):
108+
let tokenAuthInfo = externalEndpointAuthInfo as auth.TokenExternalAuthInfo;
109+
this.removeSourceFromTempNugetConfig(source);
110+
this.addSourceWithUsernamePasswordToTempNuGetConfig(source, "CustomToken", tokenAuthInfo.token);
111+
break;
112+
case (auth.ExternalAuthType.ApiKey):
113+
let apiKeyAuthInfo = externalEndpointAuthInfo as auth.ApiKeyExternalAuthInfo;
114+
this.setApiKeyForSourceInTempNuGetConfig(source, apiKeyAuthInfo.apiKey);
115+
break;
116+
default:
117+
break;
118+
}
119+
}
120+
}
121+
});
122+
}
123+
124+
private getSourcesFromTempNuGetConfig(): Q.Promise<IPackageSource[]> {
125+
// load content of the user's nuget.config
126+
let configPath: string = this.tempNugetConfigPath ? this.tempNugetConfigPath : this.nugetConfigPath;
127+
128+
if (!configPath)
129+
{
130+
return Q.resolve([]);
131+
}
132+
133+
tl.debug('Getting sources from NuGet.config in this location: ' + configPath);
134+
135+
let xmlString = fs.readFileSync(configPath).toString();
136+
137+
// strip BOM; xml parser doesn't like it
138+
if (xmlString.charCodeAt(0) === 0xFEFF) {
139+
xmlString = xmlString.substr(1);
140+
}
141+
142+
// get package sources
143+
return Q.nfcall<any>(xmlreader.read, xmlString)
144+
.then(configXml => {
145+
let packageSources = [];
146+
let packageSource: IPackageSource;
147+
let sourceKey;
148+
let sourceValue;
149+
150+
// give clearer errors if the user has set an invalid nuget.config
151+
if (!configXml.configuration) {
152+
if (configXml.packages) {
153+
throw new Error(tl.loc(
154+
"NGCommon_NuGetConfigIsPackagesConfig",
155+
this.nugetConfigPath,
156+
tl.getVariable("Task.DisplayName")));
157+
}
158+
else {
159+
throw new Error(tl.loc("NGCommon_NuGetConfigIsInvalid", this.nugetConfigPath));
160+
}
161+
}
162+
163+
if (!configXml.configuration.packageSources || !configXml.configuration.packageSources.add) {
164+
tl.warning(tl.loc("NGCommon_NoSourcesFoundInConfig", this.nugetConfigPath));
165+
return [];
166+
}
167+
168+
for (let i = 0; i < configXml.configuration.packageSources.add.count(); i++) {
169+
sourceKey = configXml.configuration.packageSources.add.at(i).attributes().key;
170+
sourceValue = configXml.configuration.packageSources.add.at(i).attributes().value;
171+
if (!sourceKey || !sourceValue) {
172+
continue;
173+
}
174+
175+
packageSource = { feedName: sourceKey, feedUri: sourceValue, isInternal: false };
176+
let isInternalFeed: boolean = this.shouldGetCredentialsForFeed(packageSource);
177+
packageSource.isInternal = isInternalFeed;
178+
packageSources.push(packageSource);
179+
}
180+
181+
return packageSources;
182+
});
183+
}
184+
185+
private removeSourceFromTempNugetConfig(packageSource: IPackageSource) {
186+
let nugetTool = ngToolRunner.createNuGetToolRunner(this.nugetPath, this.environmentSettings, this.authInfo);
187+
188+
nugetTool.arg("sources");
189+
nugetTool.arg("Remove");
190+
nugetTool.arg("-NonInteractive");
191+
nugetTool.arg("-Name");
192+
nugetTool.arg(packageSource.feedName);
193+
nugetTool.arg("-ConfigFile");
194+
nugetTool.arg(this.tempNugetConfigPath);
195+
196+
// short run, use execSync
197+
nugetTool.execSync();
198+
}
199+
200+
201+
private addSourcesToTempNugetConfigInternal(packageSources: IPackageSource[]) {
202+
packageSources.forEach((source) => {
203+
let nugetTool = ngToolRunner.createNuGetToolRunner(this.nugetPath, this.environmentSettings, this.authInfo);
204+
205+
nugetTool.arg("sources");
206+
nugetTool.arg("Add");
207+
nugetTool.arg("-NonInteractive");
208+
nugetTool.arg("-Name");
209+
nugetTool.arg(source.feedName);
210+
nugetTool.arg("-Source");
211+
nugetTool.arg(source.feedUri);
212+
nugetTool.arg("-ConfigFile");
213+
nugetTool.arg(this.tempNugetConfigPath);
214+
215+
// short run, use execSync
216+
nugetTool.execSync();
217+
});
218+
}
219+
220+
private addSourceWithUsernamePasswordToTempNuGetConfig(source: IPackageSource, username: string, password: string)
221+
{
222+
let nugetTool = ngToolRunner.createNuGetToolRunner(this.nugetPath, this.environmentSettings, this.authInfo);
223+
nugetTool.arg("sources");
224+
nugetTool.arg("Add");
225+
nugetTool.arg("-NonInteractive");
226+
nugetTool.arg("-Name");
227+
nugetTool.arg(source.feedName);
228+
nugetTool.arg("-Source");
229+
nugetTool.arg(source.feedUri);
230+
nugetTool.arg("-ConfigFile");
231+
nugetTool.arg(this.tempNugetConfigPath);
232+
nugetTool.arg("-Username");
233+
nugetTool.arg(username);
234+
nugetTool.arg("-Password");
235+
nugetTool.arg(password);
236+
237+
if (tl.osType() !== 'Windows_NT') {
238+
// only Windows supports DPAPI. Older NuGets fail to add credentials at all if DPAPI fails.
239+
nugetTool.arg("-StorePasswordInClearText");
240+
}
241+
242+
// short run, use execSync
243+
nugetTool.execSync();
244+
}
245+
246+
private setApiKeyForSourceInTempNuGetConfig(source: IPackageSource, apiKey: string)
247+
{
248+
let nugetTool = ngToolRunner.createNuGetToolRunner(this.nugetPath, this.environmentSettings, this.authInfo);
249+
nugetTool.arg("setapikey");
250+
nugetTool.arg(apiKey);
251+
nugetTool.arg("-NonInteractive");
252+
nugetTool.arg("-Source");
253+
nugetTool.arg(source.feedUri);
254+
nugetTool.arg("-ConfigFile");
255+
nugetTool.arg(this.tempNugetConfigPath);
256+
257+
// short run, use execSync
258+
nugetTool.execSync();
259+
}
260+
261+
private shouldGetCredentialsForFeed(source: IPackageSource): boolean {
262+
let uppercaseUri = source.feedUri.toUpperCase();
263+
return this.authInfo.internalAuthInfo.uriPrefixes.some(prefix => uppercaseUri.indexOf(prefix.toUpperCase()) === 0);
264+
}
265+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Placed as a separate file for the purpose of unit testing
2+
3+
export function getUtcDateString(): string {
4+
let now: Date = new Date();
5+
return `${now.getFullYear()}${now.getUTCMonth()}${now.getUTCDate()}-${now.getUTCHours()}${now.getUTCMinutes()}${now.getUTCSeconds()}`;
6+
}

0 commit comments

Comments
 (0)