Skip to content

Commit dafc900

Browse files
Creating the all-in-one NuGet task
1 parent b72e3fa commit dafc900

Some content is hidden

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

65 files changed

+3317
-668
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: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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("save nuget.config to temp config file");
34+
if (!(fs.existsSync(this.tempNugetConfigDir))) {
35+
fs.mkdirSync(this.tempNugetConfigDir);
36+
}
37+
38+
this.tempNugetConfigPath = path.join(this.tempNugetConfigDir, this.tempNugetConfigFileName);
39+
40+
if (!fs.existsSync(this.tempNugetConfigPath))
41+
{
42+
if (this.nugetConfigPath) {
43+
// don't use cp as that copies the read-only flag, and tfvc sets that on files
44+
let content = fs.readFileSync(this.nugetConfigPath);
45+
fs.writeFileSync(this.tempNugetConfigPath, content);
46+
}
47+
else {
48+
// small file, use writeFileSync
49+
fs.writeFileSync(this.tempNugetConfigPath, "<configuration/>");
50+
}
51+
}
52+
}
53+
54+
public addSourcesToTempNuGetConfig(packageSources: IPackageSource[]): void
55+
{
56+
tl.debug('Adding sources to nuget.config');
57+
this.ensureTempConfigCreated();
58+
this.addSourcesToTempNugetConfigInternal(packageSources);
59+
}
60+
61+
public async setAuthForSourcesInTempNuGetConfigAsync(): Promise<void>
62+
{
63+
tl.debug('Setting auth in the temp nuget.config');
64+
65+
let sources = await this.getSourcesFromTempNuGetConfig();
66+
if (sources.length < 1)
67+
{
68+
tl.debug('Not setting up auth for temp nuget.config as there are no sources');
69+
return;
70+
}
71+
72+
this.ensureTempConfigCreated();
73+
sources.forEach((source) => {
74+
if (source.isInternal)
75+
{
76+
if(this.authInfo.internalAuthInfo.useCredConfig)
77+
{
78+
tl.debug('Setting auth for internal source ' + source.feedUri);
79+
// Removing source first
80+
this.removeSourceFromTempNugetConfig(source);
81+
// Re-adding source with creds
82+
this.addSourceWithUsernamePasswordToTempNuGetConfig(source, "VssSessionToken", this.authInfo.internalAuthInfo.accessToken);
83+
}
84+
}
85+
// Source is external
86+
else
87+
{
88+
if (!this.authInfo.externalAuthInfo || this.authInfo.externalAuthInfo.length < 1)
89+
{
90+
return;
91+
}
92+
93+
let indexAuthInfo: number = this.authInfo.externalAuthInfo.findIndex(externalEndpoint => url.parse(externalEndpoint.packageSource.feedUri).href.toLowerCase() === url.parse(source.feedUri).href.toLowerCase());
94+
if(indexAuthInfo > -1)
95+
{
96+
let externalEndpointAuthInfo: auth.ExternalAuthInfo = this.authInfo.externalAuthInfo[indexAuthInfo];
97+
tl.debug('Setting auth for external source ' + source.feedUri);
98+
console.log(tl.loc("Info_MatchingUrlWasFoundSettingAuth") + source.feedUri);
99+
switch(externalEndpointAuthInfo.authType)
100+
{
101+
case (auth.ExternalAuthType.UsernamePassword):
102+
let usernamePwdAuthInfo = externalEndpointAuthInfo as auth.UsernamePasswordExternalAuthInfo;
103+
this.removeSourceFromTempNugetConfig(source);
104+
this.addSourceWithUsernamePasswordToTempNuGetConfig(source, usernamePwdAuthInfo.username, usernamePwdAuthInfo.password);
105+
break;
106+
case (auth.ExternalAuthType.Token):
107+
let tokenAuthInfo = externalEndpointAuthInfo as auth.TokenExternalAuthInfo;
108+
this.removeSourceFromTempNugetConfig(source);
109+
this.addSourceWithUsernamePasswordToTempNuGetConfig(source, "CustomToken", tokenAuthInfo.token);
110+
break;
111+
case (auth.ExternalAuthType.ApiKey):
112+
let apiKeyAuthInfo = externalEndpointAuthInfo as auth.ApiKeyExternalAuthInfo;
113+
this.setApiKeyForSourceInTempNuGetConfig(source, apiKeyAuthInfo.apiKey);
114+
break;
115+
default:
116+
break;
117+
}
118+
}
119+
}
120+
});
121+
}
122+
123+
private getSourcesFromTempNuGetConfig(): Q.Promise<IPackageSource[]> {
124+
// load content of the user's nuget.config
125+
let configPath: string = this.tempNugetConfigPath ? this.tempNugetConfigPath : this.nugetConfigPath;
126+
127+
if (!configPath)
128+
{
129+
return Q.resolve([]);
130+
}
131+
132+
tl.debug('Getting sources from NuGet.config in this location: ' + configPath);
133+
134+
let xmlString = fs.readFileSync(configPath).toString();
135+
136+
// strip BOM; xml parser doesn't like it
137+
if (xmlString.charCodeAt(0) === 0xFEFF) {
138+
xmlString = xmlString.substr(1);
139+
}
140+
141+
// get package sources
142+
return Q.nfcall<any>(xmlreader.read, xmlString)
143+
.then(configXml => {
144+
let packageSources = [];
145+
let packageSource: IPackageSource;
146+
let sourceKey;
147+
let sourceValue;
148+
149+
// give clearer errors if the user has set an invalid nuget.config
150+
if (!configXml.configuration) {
151+
if (configXml.packages) {
152+
throw new Error(tl.loc(
153+
"NGCommon_NuGetConfigIsPackagesConfig",
154+
this.nugetConfigPath,
155+
tl.getVariable("Task.DisplayName")));
156+
}
157+
else {
158+
throw new Error(tl.loc("NGCommon_NuGetConfigIsInvalid", this.nugetConfigPath));
159+
}
160+
}
161+
162+
if (!configXml.configuration.packageSources || !configXml.configuration.packageSources.add) {
163+
tl.warning(tl.loc("NGCommon_NoSourcesFoundInConfig", this.nugetConfigPath));
164+
return [];
165+
}
166+
167+
for (let i = 0; i < configXml.configuration.packageSources.add.count(); i++) {
168+
sourceKey = configXml.configuration.packageSources.add.at(i).attributes().key;
169+
sourceValue = configXml.configuration.packageSources.add.at(i).attributes().value;
170+
if (!sourceKey || !sourceValue) {
171+
continue;
172+
}
173+
174+
packageSource = { feedName: sourceKey, feedUri: sourceValue, isInternal: false };
175+
let isInternalFeed: boolean = this.shouldGetCredentialsForFeed(packageSource);
176+
packageSource.isInternal = isInternalFeed;
177+
packageSources.push(packageSource);
178+
}
179+
180+
return packageSources;
181+
});
182+
}
183+
184+
private removeSourceFromTempNugetConfig(packageSource: IPackageSource) {
185+
let nugetTool = ngToolRunner.createNuGetToolRunner(this.nugetPath, this.environmentSettings, this.authInfo);
186+
187+
nugetTool.arg("sources");
188+
nugetTool.arg("Remove");
189+
nugetTool.arg("-NonInteractive");
190+
nugetTool.arg("-Name");
191+
nugetTool.arg(packageSource.feedName);
192+
nugetTool.arg("-ConfigFile");
193+
nugetTool.arg(this.tempNugetConfigPath);
194+
195+
// short run, use execSync
196+
nugetTool.execSync();
197+
}
198+
199+
200+
private addSourcesToTempNugetConfigInternal(packageSources: IPackageSource[]) {
201+
packageSources.forEach((source) => {
202+
let nugetTool = ngToolRunner.createNuGetToolRunner(this.nugetPath, this.environmentSettings, this.authInfo);
203+
204+
nugetTool.arg("sources");
205+
nugetTool.arg("Add");
206+
nugetTool.arg("-NonInteractive");
207+
nugetTool.arg("-Name");
208+
nugetTool.arg(source.feedName);
209+
nugetTool.arg("-Source");
210+
nugetTool.arg(source.feedUri);
211+
nugetTool.arg("-ConfigFile");
212+
nugetTool.arg(this.tempNugetConfigPath);
213+
214+
// short run, use execSync
215+
nugetTool.execSync();
216+
});
217+
}
218+
219+
private addSourceWithUsernamePasswordToTempNuGetConfig(source: IPackageSource, username: string, password: string)
220+
{
221+
let nugetTool = ngToolRunner.createNuGetToolRunner(this.nugetPath, this.environmentSettings, this.authInfo);
222+
nugetTool.arg("sources");
223+
nugetTool.arg("Add");
224+
nugetTool.arg("-NonInteractive");
225+
nugetTool.arg("-Name");
226+
nugetTool.arg(source.feedName);
227+
nugetTool.arg("-Source");
228+
nugetTool.arg(source.feedUri);
229+
nugetTool.arg("-ConfigFile");
230+
nugetTool.arg(this.tempNugetConfigPath);
231+
nugetTool.arg("-Username");
232+
nugetTool.arg(username);
233+
nugetTool.arg("-Password");
234+
nugetTool.arg(password);
235+
236+
if (tl.osType() !== 'Windows_NT') {
237+
// only Windows supports DPAPI. Older NuGets fail to add credentials at all if DPAPI fails.
238+
nugetTool.arg("-StorePasswordInClearText");
239+
}
240+
241+
// short run, use execSync
242+
nugetTool.execSync();
243+
}
244+
245+
private setApiKeyForSourceInTempNuGetConfig(source: IPackageSource, apiKey: string)
246+
{
247+
let nugetTool = ngToolRunner.createNuGetToolRunner(this.nugetPath, this.environmentSettings, this.authInfo);
248+
nugetTool.arg("setapikey");
249+
nugetTool.arg(apiKey);
250+
nugetTool.arg("-NonInteractive");
251+
nugetTool.arg("-Source");
252+
nugetTool.arg(source.feedUri);
253+
nugetTool.arg("-ConfigFile");
254+
nugetTool.arg(this.tempNugetConfigPath);
255+
256+
// short run, use execSync
257+
nugetTool.execSync();
258+
}
259+
260+
private shouldGetCredentialsForFeed(source: IPackageSource): boolean {
261+
let uppercaseUri = source.feedUri.toUpperCase();
262+
return this.authInfo.internalAuthInfo.uriPrefixes.some(prefix => uppercaseUri.indexOf(prefix.toUpperCase()) === 0);
263+
}
264+
}
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)