Skip to content

Commit

Permalink
fix: avoid cross-geo calls (#9613)
Browse files Browse the repository at this point in the history
* WIP: avoid cross-geo calls

* Use cluster category for credentials insead of hardcoded URLs

* WIP publish adjustments

* remove tests

* fix more tests

* handle optional cluster category

* make ts happy

---------

Co-authored-by: Chris Whitten <[email protected]>
  • Loading branch information
OEvgeny and cwhitten authored Jan 21, 2025
1 parent cce5b36 commit 1cdb41d
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 177 deletions.
5 changes: 5 additions & 0 deletions Composer/packages/electron-server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,11 @@ async function run() {
};
});

ipcMain.handle('deeplink', async (evt, url: string) => {
const deeplink = parseDeepLinkUrl(url);
await getMainWindow()?.webContents.loadURL(getBaseUrl() + deeplink);
});

await main();
setTimeout(() => startApp(signalThatMainWindowIsShowing), 500);
await initApp();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ export const PVA_GOV_APP_ID = '9315aedd-209b-43b3-b149-2abff6a95d59';
export const PVA_GCC_HIGH_APP_ID = '69c6e40c-465f-4154-987d-da5cba10734e';

export type PowerVirtualAgentsMetadata = IContentProviderMetadata & {
clusterCategory?:
| 'Dev'
| 'Prv'
| 'Test'
| 'Preprod'
| 'FirstRelease'
| 'Prod'
| 'Gov'
| 'High'
| 'DoD'
| 'Mooncake'
| 'Ex'
| 'Rx';
baseUrl: string;
botId: string;
dialogId?: string;
Expand All @@ -32,22 +45,35 @@ export type PowerVirtualAgentsMetadata = IContentProviderMetadata & {
};

const getAuthCredentials = (baseUrl: string, metadata: PowerVirtualAgentsMetadata) => {
const clusterCategory =
(process.env.COMPOSER_PVA_CLUSTER as typeof metadata.clusterCategory) ?? metadata.clusterCategory;
const url = new URL(baseUrl);
if (url.hostname.includes('.int.') || url.hostname.includes('.ppe.')) {
if (
(clusterCategory && ['Test', 'Preprod', 'Dev'].includes(clusterCategory)) ||
url.hostname.includes('.int.') ||
url.hostname.includes('.ppe.') ||
url.hostname.includes('.test.')
) {
log('Using INT / PPE auth credentials.');
return {
clientId: COMPOSER_1P_APP_ID,
scopes: [`${PVA_TEST_APP_ID}/.default`],
targetResource: PVA_TEST_APP_ID,
};
} else if (url.hostname.includes('gcc.api.powerva.microsoft.us')) {
} else if (
(clusterCategory && ['Gov'].includes(clusterCategory)) ||
url.hostname.includes('gcc.api.powerva.microsoft.us')
) {
log('Using GCC auth credentials.');
return {
clientId: COMPOSER_1P_APP_ID,
scopes: [`${PVA_GOV_APP_ID}/.default`],
targetResource: PVA_GOV_APP_ID,
};
} else if (url.hostname.includes('high.api.powerva.microsoft.us')) {
} else if (
(clusterCategory && ['High'].includes(clusterCategory)) ||
url.hostname.includes('high.api.powerva.microsoft.us')
) {
log('Using GCC High auth credentials.');
return {
authority: `https://login.microsoftonline.us/${metadata.tenantId}`,
Expand All @@ -56,55 +82,14 @@ const getAuthCredentials = (baseUrl: string, metadata: PowerVirtualAgentsMetadat
targetResource: PVA_GCC_HIGH_APP_ID,
};
}
log('Using PROD auth credentials.');
log(`Using PROD auth credentials.\nCategory: ${clusterCategory}\nURL: ${baseUrl}`);
return {
clientId: COMPOSER_1P_APP_ID,
scopes: [`${PVA_PROD_APP_ID}/.default`],
targetResource: PVA_PROD_APP_ID,
};
};

const getBaseUrl = () => {
const pvaEnv = (process.env.COMPOSER_PVA_ENV || '').toLowerCase();
switch (pvaEnv) {
case 'prod': {
const url = 'https://powerva.microsoft.com/api/botmanagement/v1';
log('PROD env detected, grabbing PVA content from %s', url);
return url;
}

case 'ppe': {
const url = 'https://bots.ppe.customercareintelligence.net/api/botmanagement/v1';
log('PPE env detected, grabbing PVA content from %s', url);
return url;
}

case 'int': {
const url = 'https://bots.int.customercareintelligence.net/api/botmanagement/v1';
log('INT env detected, grabbing PVA content from %s', url);
return url;
}

case 'gcc': {
const url = 'https://gcc.api.powerva.microsoft.us/api/botmanagement/v1';
log('GCC env detected, grabbing PVA content from %s', url);
return url;
}

case 'gcc-high': {
const url = 'https://high.api.powerva.microsoft.us/api/botmanagement/v1';
log('GCC High env detected, grabbing PVA content from %s', url);
return url;
}

default: {
const url = 'https://bots.int.customercareintelligence.net/api/botmanagement/v1';
log('No env flag detected, grabbing PVA content from %s', url);
return url;
}
}
};

function prettyPrintError(err: string | Error): string {
if (typeof err === 'string') {
return err;
Expand Down Expand Up @@ -177,7 +162,7 @@ export class PowerVirtualAgentsProvider extends ExternalContentProvider<PowerVir
try {
// login to the 1P app and get an access token
const { baseUrl } = this.metadata;
const authCredentials = getAuthCredentials(baseUrl || getBaseUrl(), this.metadata);
const authCredentials = getAuthCredentials(baseUrl, this.metadata);
const accessToken = await authService.getAccessToken(authCredentials);
if (accessToken === '') {
throw 'User cancelled login flow.';
Expand All @@ -190,7 +175,7 @@ export class PowerVirtualAgentsProvider extends ExternalContentProvider<PowerVir

private getContentUrl(): string {
const { envId, baseUrl, botId } = this.metadata;
return `${baseUrl || getBaseUrl()}/environments/${envId}/bots/${botId}/composer/content?includeTopics=true`;
return `${baseUrl}/environments/${envId}/bots/${botId}/composer/content?includeTopics=true`;
}

private async getRequestHeaders() {
Expand All @@ -206,7 +191,7 @@ export class PowerVirtualAgentsProvider extends ExternalContentProvider<PowerVir
private getDeepLink(): string {
// use metadata (if provided) to create a deep link to a specific dialog / trigger / action etc. after opening bot.
let deepLink = '';
const { dialogId, triggerId, actionId = '' } = this.metadata;
const { dialogId, triggerId, actionId = '', clusterCategory } = this.metadata;

if (dialogId) {
deepLink += `dialogs/${dialogId}`;
Expand All @@ -219,6 +204,9 @@ export class PowerVirtualAgentsProvider extends ExternalContentProvider<PowerVir
`"${actionId}"`,
)}]`;
}
if (clusterCategory) {
deepLink += deepLink.includes('?') ? '&' : '?' + `cluster=${clusterCategory}`;
}
// base64 encode to make parsing on the client side easier
return Buffer.from(deepLink, 'utf-8').toString('base64');
}
Expand Down
13 changes: 0 additions & 13 deletions extensions/pvaPublish/src/node/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ export const AUTH_CREDENTIALS = {
// electron auth flow
targetResource: 'a522f059-bb65-47c0-8934-7db6e5286414',
},
PPE: {
clientId: COMPOSER_1P_APP_ID,
scopes: ['a522f059-bb65-47c0-8934-7db6e5286414/.default'],
targetResource: 'a522f059-bb65-47c0-8934-7db6e5286414',
},
PROD: {
clientId: COMPOSER_1P_APP_ID,
scopes: ['96ff4394-9197-43aa-b393-6a41652e21f8/.default'],
Expand All @@ -36,11 +31,3 @@ export const AUTH_CREDENTIALS = {
targetResource: '69c6e40c-465f-4154-987d-da5cba10734e',
},
};

export const BASE_URLS = {
INT: 'https://bots.int.customercareintelligence.net/',
PPE: 'https://bots.ppe.customercareintelligence.net/',
PROD: 'https://powerva.microsoft.com/',
GCC: 'https://gcc.api.powerva.microsoft.us/',
GCC_HIGH: 'https://high.api.powerva.microsoft.us/',
};
28 changes: 19 additions & 9 deletions extensions/pvaPublish/src/node/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { RequestInit } from 'node-fetch';

import fetch from './fetch';
import { PVAPublishJob, PublishConfig, UserIdentity, PublishState, PublishHistory, PullResponse } from './types';
import { getAuthCredentials, getBaseUrl } from './utils';
import { getAuthCredentials } from './utils';
import { logger } from './logger';
import { API_VERSION } from './constants';

Expand All @@ -35,14 +35,18 @@ export const publish = async (
envId,
tenantId,
deleteMissingDependencies = false, // publish behavior
clusterCategory,
} = config;
const { comment = '' } = metadata;

try {
logger.log('Starting publish to Power Virtual Agents.');
// authenticate with PVA
const base = baseUrl || getBaseUrl();
const creds = getAuthCredentials(base, tenantId);
const base = baseUrl;
if (!base) {
throw new Error('Base URL is not supplied in published target');
}
const creds = getAuthCredentials(base, tenantId, clusterCategory);
const accessToken = await getAccessToken(creds);

// write the .zip to a buffer in memory
Expand Down Expand Up @@ -145,6 +149,7 @@ export const getStatus = async (
botId,
envId,
tenantId,
clusterCategory,
} = config;
const botProjectId = project.id || '';

Expand All @@ -161,8 +166,11 @@ export const getStatus = async (

try {
// authenticate with PVA
const base = baseUrl || getBaseUrl();
const creds = getAuthCredentials(base, tenantId);
const base = baseUrl;
if (!base) {
throw new Error('Base URL is not supplied in published target');
}
const creds = getAuthCredentials(base, tenantId, clusterCategory);
const accessToken = await getAccessToken(creds);

// check the status for the publish job
Expand Down Expand Up @@ -218,12 +226,13 @@ export const history = async (
botId,
envId,
tenantId,
clusterCategory,
} = config;

try {
// authenticate with PVA
const base = baseUrl || getBaseUrl();
const creds = getAuthCredentials(base, tenantId);
const base = baseUrl;
const creds = getAuthCredentials(base, tenantId, clusterCategory);
const accessToken = await getAccessToken(creds);

// get the publish history for the bot
Expand Down Expand Up @@ -253,11 +262,12 @@ export const pull = async (
botId,
envId,
tenantId,
clusterCategory,
} = config;
try {
// authenticate with PVA
const base = baseUrl || getBaseUrl();
const creds = getAuthCredentials(base, tenantId);
const base = baseUrl;
const creds = getAuthCredentials(base, tenantId, clusterCategory);
const accessToken = await getAccessToken(creds);

// fetch zip containing bot content
Expand Down
15 changes: 15 additions & 0 deletions extensions/pvaPublish/src/node/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@

import { PublishResult } from '@botframework-composer/types';

export type ClusterCategory =
| 'Dev'
| 'Prv'
| 'Test'
| 'Preprod'
| 'FirstRelease'
| 'Prod'
| 'Gov'
| 'High'
| 'DoD'
| 'Mooncake'
| 'Ex'
| 'Rx';

export type PVAPublishJob = {
comment: string;
diagnostics: DiagnosticInfo[];
Expand Down Expand Up @@ -38,6 +52,7 @@ export type PublishConfig = {
fullSettings: any;
profileName: string;
tenantId: string;
clusterCategory: ClusterCategory;
};

export type PublishState =
Expand Down
50 changes: 3 additions & 47 deletions extensions/pvaPublish/src/node/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { AUTH_CREDENTIALS, BASE_URLS } from './constants';
import { getAuthCredentials, getBaseUrl } from './utils';

describe('should return the proper PVA base URL for the environment', () => {
let envBackup;
beforeAll(() => {
envBackup = { ...process.env };
});

beforeEach(() => {
Object.assign(process.env, { COMPOSER_PVA_PUBLISH_ENV: '' });
});

afterAll(() => {
Object.assign(process.env, envBackup); // restore the platform
});

it('fallback to prod', () => {
expect(getBaseUrl()).toBe(BASE_URLS.PROD);
});

it('int', () => {
Object.assign(process.env, { COMPOSER_PVA_PUBLISH_ENV: 'INT' });
expect(getBaseUrl()).toBe(BASE_URLS.INT);
});

it('ppe', () => {
Object.assign(process.env, { COMPOSER_PVA_PUBLISH_ENV: 'PPE' });
expect(getBaseUrl()).toBe(BASE_URLS.PPE);
});

it('prod', () => {
Object.assign(process.env, { COMPOSER_PVA_PUBLISH_ENV: 'PROD' });
expect(getBaseUrl()).toBe(BASE_URLS.PROD);
});

it('gcc', () => {
Object.assign(process.env, { COMPOSER_PVA_PUBLISH_ENV: 'GCC' });
expect(getBaseUrl()).toBe(BASE_URLS.GCC);
});

it('gcc high', () => {
Object.assign(process.env, { COMPOSER_PVA_PUBLISH_ENV: 'GCC-HIGH' });
expect(getBaseUrl()).toBe(BASE_URLS.GCC_HIGH);
});
});
import { AUTH_CREDENTIALS } from './constants';
import { getAuthCredentials } from './utils';

describe('it should return the proper PVA auth parameters for the base URL', () => {
it('fallback to prod', () => {
Expand All @@ -64,7 +20,7 @@ describe('it should return the proper PVA auth parameters for the base URL', ()

it('ppe', () => {
const url = 'https://bots.ppe.customercareintelligence.net/api/botmanagement/v1';
expect(getAuthCredentials(url)).toEqual(AUTH_CREDENTIALS.PPE);
expect(getAuthCredentials(url)).toEqual(AUTH_CREDENTIALS.INT);
});

it('prod', () => {
Expand Down
Loading

0 comments on commit 1cdb41d

Please sign in to comment.