Skip to content

Commit 8500395

Browse files
nturinskiNathan TurinskiCopilot
authored
Setup azfed credential provider in extension code for client testing (#1378)
* Setup azfed credential provider in extension code for client testing * Add the old validation code for serviceConnection properties * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Nathan Turinski <naturins@microsoft.comm> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 1a60a63 commit 8500395

File tree

4 files changed

+75
-50
lines changed

4 files changed

+75
-50
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.md in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import type { AzureSubscriptionProvider } from "@microsoft/vscode-azext-azureauth";
7+
import type { AzureDevOpsSubscriptionProviderInitializer } from "@microsoft/vscode-azext-azureauth/azdo";
8+
9+
/**
10+
* Reads Azure DevOps federated credential configuration from environment variables
11+
* and returns a factory that creates an {@link AzureDevOpsSubscriptionProvider}.
12+
*
13+
* Expected environment variables (set by the shared pipeline template in `vscode-azuretools`):
14+
* - `AzCode_ServiceConnectionID` — the resource ID of the ADO federated service connection
15+
* - `AzCode_ServiceConnectionDomain` — the tenant ID of the service connection
16+
* - `AzCode_ServiceConnectionClientID` — the service principal (application client) ID
17+
*
18+
* The factory also calls `signIn()` on the first invocation so the provider is
19+
* ready to use immediately.
20+
*/
21+
export function createAzureDevOpsSubscriptionProviderFactory(): () => Promise<AzureSubscriptionProvider> {
22+
const serviceConnectionId: string | undefined = process.env['AzCode_ServiceConnectionID'];
23+
const tenantId: string | undefined = process.env['AzCode_ServiceConnectionDomain'];
24+
const clientId: string | undefined = process.env['AzCode_ServiceConnectionClientID'];
25+
26+
if (!serviceConnectionId || !tenantId || !clientId) {
27+
throw new Error(`Using Azure DevOps federated credentials, but federated service connection is not configured\n
28+
process.env.AzCode_ServiceConnectionID: ${serviceConnectionId ? "✅" : "❌"}\n
29+
process.env.AzCode_ServiceConnectionDomain: ${tenantId ? "✅" : "❌"}\n
30+
process.env.AzCode_ServiceConnectionClientID: ${clientId ? "✅" : "❌"}\n
31+
`);
32+
}
33+
34+
const initializer: AzureDevOpsSubscriptionProviderInitializer = {
35+
serviceConnectionId,
36+
tenantId,
37+
clientId,
38+
};
39+
40+
let providerPromise: Promise<AzureSubscriptionProvider> | undefined;
41+
42+
return () => {
43+
providerPromise ??= (async () => {
44+
// Dynamic import so the AzDO-specific module (and its @azure/identity dependency)
45+
// is only loaded when federated credentials are actually in use.
46+
const { AzureDevOpsSubscriptionProvider } = await import("@microsoft/vscode-azext-azureauth/azdo");
47+
const provider = new AzureDevOpsSubscriptionProvider(initializer);
48+
const signedIn = await provider.signIn();
49+
if (!signedIn) {
50+
throw new Error("Azure DevOps sign-in failed during subscription provider initialization.");
51+
}
52+
return provider;
53+
})();
54+
return providerPromise;
55+
};
56+
}

src/services/getSubscriptionProviderFactory.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,23 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import type { AzureSubscriptionProvider } from "@microsoft/vscode-azext-azureauth";
7+
import { createAzureDevOpsSubscriptionProviderFactory } from "./AzureDevOpsSubscriptionProvider";
78
import { createVSCodeAzureSubscriptionProviderFactory } from "./VSCodeAzureSubscriptionProvider";
89

910
/**
1011
* Returns a factory function that creates a subscription provider, satisfying the `AzureSubscriptionProvider` interface.
1112
*
12-
* For nightly tests that require Azure DevOps federated credentials, use the test API to set
13-
* `ext.testing.overrideAzureSubscriptionProvider` with an AzDO provider factory instead.
13+
* When running in an Azure DevOps pipeline with federated credentials enabled
14+
* (via the `AzCode_UseAzureFederatedCredentials` environment variable), this returns an
15+
* {@link AzureDevOpsSubscriptionProvider}-based factory so that client extensions that depend
16+
* on the Resources extension API also get the correct subscription provider without needing
17+
* to set it up themselves.
1418
*/
1519
export function getSubscriptionProviderFactory(): () => Promise<AzureSubscriptionProvider> {
20+
const useAzureFederatedCredentials = !/^(false|0)?$/i.test(process.env['AzCode_UseAzureFederatedCredentials'] || '');
21+
if (useAzureFederatedCredentials) {
22+
return createAzureDevOpsSubscriptionProviderFactory();
23+
}
24+
1625
return createVSCodeAzureSubscriptionProviderFactory();
1726
}

test/nightly/global.nightly.test.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import * as vscode from 'vscode';
77
import { longRunningTestsEnabled } from '../global.test';
8-
import { setupAzureDevOpsSubscriptionProvider } from '../utils/azureDevOpsSubscriptionProvider';
98

109
export const resourceGroupsToDelete: string[] = [];
1110

@@ -14,11 +13,6 @@ suiteSetup(async function (this: Mocha.Context): Promise<void> {
1413
if (longRunningTestsEnabled) {
1514
this.timeout(2 * 60 * 1000);
1615

17-
// Set up Azure DevOps subscription provider for federated credentials
18-
const useAzureFederatedCredentials: boolean = !/^(false|0)?$/i.test(process.env['AzCode_UseAzureFederatedCredentials'] || '');
19-
if (useAzureFederatedCredentials) {
20-
await setupAzureDevOpsSubscriptionProvider();
21-
}
2216

2317
await vscode.commands.executeCommand('azureResourceGroups.logIn');
2418
}
Lines changed: 8 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,23 @@
11
/*---------------------------------------------------------------------------------------------
22
* Copyright (c) Microsoft Corporation. All rights reserved.
3-
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
3+
* Licensed under the MIT License. See License.md in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { createAzureDevOpsSubscriptionProviderFactory } from "../../src/services/AzureDevOpsSubscriptionProvider";
67
import { getCachedTestApi } from "./testApiAccess";
78

89
/**
9-
* Sets up the Azure DevOps subscription provider for nightly tests.
10-
* This reads credentials from environment variables and configures the test API override.
10+
* Re-establishes the Azure DevOps subscription provider via the test API.
1111
*
12-
* Required environment variables:
13-
* - AzCode_ServiceConnectionID: The Azure DevOps service connection ID
14-
* - AzCode_ServiceConnectionDomain: The tenant/domain ID
15-
* - AzCode_ServiceConnectionClientID: The client ID for authentication
16-
*
17-
* @throws Error if any required environment variables are missing
12+
* The primary AzDO provider setup now happens at extension activation time
13+
* (see {@link getSubscriptionProviderFactory}). This helper is only needed in test
14+
* suites that first clear overrides (e.g. after mock-based tests) and need to
15+
* restore the AzDO provider for subsequent integration tests.
1816
*/
1917
export async function setupAzureDevOpsSubscriptionProvider(): Promise<void> {
20-
const serviceConnectionId: string | undefined = process.env['AzCode_ServiceConnectionID'];
21-
const domain: string | undefined = process.env['AzCode_ServiceConnectionDomain'];
22-
const clientId: string | undefined = process.env['AzCode_ServiceConnectionClientID'];
23-
24-
if (!serviceConnectionId || !domain || !clientId) {
25-
throw new Error(`Using Azure DevOps federated credentials, but federated service connection is not configured\n
26-
process.env.AzCode_ServiceConnectionID: ${serviceConnectionId ? "✅" : "❌"}\n
27-
process.env.AzCode_ServiceConnectionDomain: ${domain ? "✅" : "❌"}\n
28-
process.env.AzCode_ServiceConnectionClientID: ${clientId ? "✅" : "❌"}\n
29-
`);
30-
}
31-
32-
// Dynamic import to avoid loading AzDO dependencies unless actually needed
33-
const { createAzureDevOpsSubscriptionProviderFactory } = await import("@microsoft/vscode-azext-azureauth/azdo");
34-
35-
const initializer = {
36-
serviceConnectionId,
37-
tenantId: domain,
38-
clientId,
39-
};
40-
41-
const factory = createAzureDevOpsSubscriptionProviderFactory(initializer);
42-
43-
// Create the provider instance now so we can return it synchronously
18+
const factory = createAzureDevOpsSubscriptionProviderFactory();
4419
const provider = await factory();
4520

46-
// Sign in to establish the token credential.
47-
// This must be done before the provider can return subscriptions.
48-
const signedIn = await provider.signIn();
49-
50-
if (!signedIn) {
51-
throw new Error('Failed to sign in with Azure DevOps federated credentials');
52-
}
53-
54-
// Set the override via the test API
5521
const testApi = getCachedTestApi();
5622
testApi.testing.setOverrideAzureSubscriptionProvider(() => provider);
5723
}

0 commit comments

Comments
 (0)