Skip to content

Commit 53463d4

Browse files
Add workloadidentityuser Service connection support to PipAuthenticateV1 (#20108)
* Reference artifacts-common * init * update preprocessor value * create wif buildgen with pip changes * bump libraries * add pip only to makeoptions * remove new inputs from default * remove new inputs from generated task * Fix for missing Node20 in versionmap. Unblock node10 build in default task * Enable Task Json Inputs Override * BuildConfigGen: resources.resjson is generated by make.js - do not verify BuildConfigGen: Added loc.json for taskJsonOverride BuildConfigGen: Fix for verifying two writes to the same file * fix l0 tests --------- Co-authored-by: Merlyn Oppenheim <[email protected]>
1 parent 0047948 commit 53463d4

Some content is hidden

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

64 files changed

+5133
-1814
lines changed

BuildConfigGen/EnsureUpdateModeVerifier.cs

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ internal class EnsureUpdateModeVerifier
1414
private List<string> VerifyErrors = new List<string>();
1515
internal Dictionary<string, string> CopiedFilesToCheck = new Dictionary<string, string>();
1616
internal Dictionary<string, string> RedirectedToTempl = new Dictionary<string, string>();
17+
private HashSet<string> tempsToKeep = new HashSet<string>();
1718

1819
public EnsureUpdateModeVerifier(bool verifyOnly)
1920
{
@@ -35,22 +36,38 @@ public IEnumerable<string> GetVerifyErrors(bool skipContentCheck)
3536
string? sourceFile;
3637
string procesed = "";
3738

39+
string? tempToKeep = null;
3840
if (RedirectedToTempl.TryGetValue(r.Key, out sourceFile))
3941
{
40-
procesed = "(processed) ";
42+
procesed = $"(processed temp={sourceFile}) ";
43+
tempToKeep = sourceFile;
4144
}
4245
else
4346
{
4447
sourceFile = r.Value;
4548
}
4649

47-
if (Helpers.FilesEqual(sourceFile, r.Key))
50+
FileInfo fi = new FileInfo(r.Key);
51+
52+
if (fi.Name.Equals("resources.resjson", StringComparison.OrdinalIgnoreCase))
4853
{
49-
// if overwrite and content match, everything is good! Verification passed.
54+
// resources.resjson is generated by make.js and does not need to be verified
55+
// it can differ between configs if configs have different inputs (causes verifier to fail);
5056
}
5157
else
5258
{
53-
yield return $"Content doesn't match {r.Value} {procesed}to {r.Key} (overwrite=true). Dest file doesn't match {procesed}source.";
59+
if (Helpers.FilesEqual(sourceFile, r.Key))
60+
{
61+
// if overwrite and content match, everything is good! Verification passed.
62+
}
63+
else
64+
{
65+
if (tempToKeep != null)
66+
{
67+
this.tempsToKeep.Add(tempToKeep!);
68+
}
69+
yield return $"Content doesn't match {r.Value} {procesed}to {r.Key} (overwrite=true). Dest file doesn't match source.";
70+
}
5471
}
5572
}
5673
}
@@ -62,9 +79,12 @@ public void CleanupTempFiles()
6279
foreach (var f in RedirectedToTempl.Values)
6380
{
6481
count++;
65-
if (File.Exists(f))
82+
if (!tempsToKeep.Contains(f))
6683
{
67-
File.Delete(f);
84+
if (File.Exists(f))
85+
{
86+
File.Delete(f);
87+
}
6888
}
6989
}
7090

@@ -137,10 +157,12 @@ internal void WriteAllText(string path, string contents, bool suppressValidation
137157
{
138158
string? tempFilePath;
139159

140-
if (!RedirectedToTempl.TryGetValue(path, out tempFilePath))
160+
string normalizedPath = NormalizeFile(path);
161+
162+
if (!RedirectedToTempl.TryGetValue(normalizedPath, out tempFilePath))
141163
{
142164
tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
143-
RedirectedToTempl.Add(path, tempFilePath);
165+
RedirectedToTempl.Add(normalizedPath, tempFilePath);
144166
}
145167

146168
//Console.WriteLine($"writing to tempFilePath={tempFilePath}");
@@ -240,11 +262,21 @@ private string ResolveFile(string filePath)
240262
filePath = NormalizeFile(filePath);
241263

242264
string? sourceFile = null, tempFile = null;
243-
if (CopiedFilesToCheck.TryGetValue(filePath, out sourceFile))
265+
266+
if (RedirectedToTempl.TryGetValue(filePath, out tempFile))
267+
{
268+
// We'll get here if we're reading a file that was written to in WriteAllText
269+
}
270+
else
244271
{
245-
if (RedirectedToTempl.TryGetValue(sourceFile, out tempFile))
272+
if (CopiedFilesToCheck.TryGetValue(filePath, out sourceFile))
246273
{
247-
// do nothing
274+
// We'll get here if we're reading a file that was copied
275+
276+
if (RedirectedToTempl.TryGetValue(sourceFile, out tempFile))
277+
{
278+
// We'll get here if we're reading a file that was copied and then written to in WriteAllText
279+
}
248280
}
249281
}
250282

BuildConfigGen/Program.cs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ public record ConfigRecord(string name, string constMappingKey, bool isDefault,
5151
public static readonly ConfigRecord Node20_229_13 = new ConfigRecord(name: nameof(Node20_229_13), constMappingKey: "Node20_229_13", isDefault: false, isNode: true, nodePackageVersion: "^20.11.0", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true);
5252
public static readonly ConfigRecord Node20_229_14 = new ConfigRecord(name: nameof(Node20_229_14), constMappingKey: "Node20_229_14", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true);
5353
public static readonly ConfigRecord WorkloadIdentityFederation = new ConfigRecord(name: nameof(WorkloadIdentityFederation), constMappingKey: "WorkloadIdentityFederation", isDefault: false, isNode: true, nodePackageVersion: "^16.11.39", isWif: true, nodeHandler: "Node16", preprocessorVariableName: "WORKLOADIDENTITYFEDERATION", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: false, writeNpmrc: false);
54-
public static ConfigRecord[] Configs = { Default, Node16, Node16_225, Node20, Node20_228, Node20_229_1, Node20_229_2, Node20_229_3, Node20_229_4, Node20_229_5, Node20_229_6, Node20_229_7, Node20_229_8, Node20_229_9, Node20_229_10, Node20_229_11, Node20_229_12, Node20_229_13, Node20_229_14, WorkloadIdentityFederation };
54+
public static readonly ConfigRecord wif_242 = new ConfigRecord(name: nameof(wif_242), constMappingKey: "wif_242", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: true, nodeHandler: "Node20_1", preprocessorVariableName: "WIF", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Wif", writeNpmrc: true);
55+
public static ConfigRecord[] Configs = { Default, Node16, Node16_225, Node20, Node20_228, Node20_229_1, Node20_229_2, Node20_229_3, Node20_229_4, Node20_229_5, Node20_229_6, Node20_229_7, Node20_229_8, Node20_229_9, Node20_229_10, Node20_229_11, Node20_229_12, Node20_229_13, Node20_229_14, WorkloadIdentityFederation, wif_242 };
5556
}
5657

5758
static List<string> notSyncronizedDependencies = [];
@@ -98,7 +99,6 @@ private static void MainInner(string? task, string? configs, int? currentSprint,
9899
else
99100
{
100101
NotNullOrThrow(task, "Task is required");
101-
NotNullOrThrow(configs, "Configs is required");
102102
}
103103

104104
string currentDir = Environment.CurrentDirectory;
@@ -142,6 +142,13 @@ private static void MainInner(string? task, string? configs, int? currentSprint,
142142
// 3. Ideally default windows exception will occur and errors reported to WER/watson. I'm not sure this is happening, perhaps DragonFruit is handling the exception
143143
foreach (var t in task!.Split(',', '|'))
144144
{
145+
if (configs == null)
146+
{
147+
var tasks = MakeOptionsReader.ReadMakeOptions(gitRootPath);
148+
var taskMakeOptions = tasks[t];
149+
configs = string.Join('|', taskMakeOptions.Configs);
150+
}
151+
145152
MainUpdateTask(t, configs!, writeUpdates, currentSprint, debugConfGen);
146153
}
147154
}
@@ -421,10 +428,11 @@ private static void MainUpdateTaskInner(
421428

422429
HandlePreprocessingInTarget(taskOutput, config, validateAndWriteChanges: true, out _);
423430

431+
WriteWIFInputTaskJson(taskOutput, config, "task.json", isLoc: false);
432+
WriteWIFInputTaskJson(taskOutput, config, "task.loc.json", isLoc: true);
424433
WriteTaskJson(taskOutput, configTaskVersionMapping, config, "task.json");
425434
WriteTaskJson(taskOutput, configTaskVersionMapping, config, "task.loc.json");
426435
}
427-
428436
WriteInputTaskJson(taskTargetPath, configTaskVersionMapping, "task.json");
429437
WriteInputTaskJson(taskTargetPath, configTaskVersionMapping, "task.loc.json");
430438

@@ -654,6 +662,24 @@ private static void WriteTaskJson(string taskPath, Dictionary<Config.ConfigRecor
654662
ensureUpdateModeVerifier!.WriteAllText(outputTaskPath, outputTaskNode.ToJsonString(jso), suppressValidationErrorIfTargetPathDoesntExist: false);
655663
}
656664

665+
private static void WriteWIFInputTaskJson(string taskPath, Config.ConfigRecord config, string fileName, bool isLoc)
666+
{
667+
if (!config.isWif)
668+
{
669+
return;
670+
}
671+
672+
string taskJsonOverridePath = Path.Combine(taskPath, isLoc ? "taskJsonOverride.loc.json" : "taskJsonOverride.json");
673+
JsonNode inputTaskNode = JsonNode.Parse(ensureUpdateModeVerifier!.FileReadAllText(taskJsonOverridePath))!;
674+
var clonedArray = JsonNode.Parse(inputTaskNode["inputs"]!.ToJsonString())!.AsArray();
675+
676+
string outputTaskPath = Path.Combine(taskPath, fileName);
677+
JsonNode outputTaskNode = JsonNode.Parse(ensureUpdateModeVerifier!.FileReadAllText(outputTaskPath))!;
678+
outputTaskNode["inputs"] = clonedArray;
679+
680+
ensureUpdateModeVerifier!.WriteAllText(outputTaskPath, outputTaskNode.ToJsonString(jso), suppressValidationErrorIfTargetPathDoesntExist: false);
681+
}
682+
657683
private static void WriteNodePackageJson(string taskOutputNode, string nodeVersion, bool shouldUpdateTypescript)
658684
{
659685
string outputNodePackagePath = Path.Combine(taskOutputNode, "package.json");

BuildConfigGen/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"profiles": {
33
"BuildConfigGen": {
44
"commandName": "Project",
5-
"commandLineArgs": "--write-updates --configs \"Node20\" --task MavenV3"
5+
"commandLineArgs": " --task PipAuthenticateV1 "
66
}
77
}
88
}

Tasks/PipAuthenticateV1/Strings/resources.resjson/en-US/resources.resjson

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,25 @@
66
"loc.group.displayName.feedAuthentication": "Feeds and Authentication",
77
"loc.input.label.artifactFeeds": "My feeds (select below)",
88
"loc.input.help.artifactFeeds": "Select feeds to authenticate present in this organization",
9+
"loc.input.label.workloadIdentityServiceConnection": "'Entra Workload ID-backed Azure DevOps user' Service Connection",
10+
"loc.input.help.workloadIdentityServiceConnection": "If this is set, feedUrl is required. All other inputs are ignored.",
11+
"loc.input.label.feedUrl": "Azure Artifacts Feeds url.",
12+
"loc.input.help.feedUrl": "If this is set, workloadIdentityServiceConnection is required. All other inputs are ignored. Not compatible with pythonDownloadServiceConnections. ",
913
"loc.input.label.pythonDownloadServiceConnections": "Feeds from external organizations",
1014
"loc.input.help.pythonDownloadServiceConnections": "Select endpoints to authenticate outside this organization.",
1115
"loc.input.label.onlyAddExtraIndex": "Don't set primary index URL",
1216
"loc.input.help.onlyAddExtraIndex": "If this is set to true, no feed will be set as the primary index URL. All of the configured feeds/endpoints will be set as extra index URLs. Defaults to false.",
1317
"loc.messages.Info_AddingInternalFeeds": "Adding auth information for %s internal feed(s).",
1418
"loc.messages.Info_AddingExternalFeeds": "Adding auth information for %s external endpoint.",
19+
"loc.messages.Info_AddingFederatedFeedAuth": "Adding auth information from service connection %s for feed %s",
1520
"loc.messages.Info_SuccessAddingAuth": "Successfully added auth for %s internal feeds and %s external endpoint.",
21+
"loc.messages.Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s.",
1622
"loc.messages.Info_AddingPasswordAuthEntry": "Adding username password auth entry for feed %s",
1723
"loc.messages.Info_AddingTokenAuthEntry": "Adding token auth entry for feed %s",
1824
"loc.messages.Error_FailedToParseFeedUrlAndAuth": "Failed to parse the feed url and add auth information. %s",
1925
"loc.messages.FailedToGetPackagingUri": "Unable to get packaging uri, using default collection uri.",
26+
"loc.messages.FailedToGetServiceConnectionAuth": "Unable to get federated credentials from service connection: %s.",
2027
"loc.messages.FailedToAddAuthentication": "Failed to add authentication.",
2128
"loc.messages.Warn_TooManyFeedEntries": "Too many feed entries for auth. Please reduce the number of repositories in the task.",
22-
"loc.messages.Warning_SessionCreationFailed": "Could not create provenance session: %s"
29+
"loc.messages.Warning_SessionCreationFailed": "Could not create provenance session: %s"
2330
}

Tasks/PipAuthenticateV1/Tests/L0.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as path from 'path';
22
import * as assert from 'assert';
33
import * as ttm from 'azure-pipelines-task-lib/mock-test';
4-
import * as tl from "azure-pipelines-task-lib";
54

65
describe('Pip Authenticate V1 Suite', function () {
76
this.timeout(parseInt(process.env.TASK_TEST_TIMEOUT) || 10000);
@@ -16,12 +15,10 @@ describe('Pip Authenticate V1 Suite', function () {
1615
let tp = path.join(__dirname, './setAuthInternalFeed.js')
1716
let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);
1817

19-
tr.run();
18+
tr.runAsync();
2019
assert(tr.invokedToolCount == 0, 'no tool should be invoked.');
2120
assert(tr.succeeded, 'should have succeeded');
2221
assert.equal(tr.errorIssues.length, 0, "should have no errors");
23-
assert(tr.stdOutContained('Successfully added auth for 1 internal feeds and 0 external endpoint'),
24-
'it should have succeeded in adding auth for 1 feed');
2522
done();
2623
});
2724
});

0 commit comments

Comments
 (0)