From 352e54c70997c47bac93f130421ffec9cb6f1a0b Mon Sep 17 00:00:00 2001 From: Josh Gummersall <1235378+joshgummersall@users.noreply.github.com> Date: Thu, 18 Mar 2021 12:13:07 -0700 Subject: [PATCH] fix: runtime parity (#3412) * fix: use __ env separator * fix: add blobs/cosmos storage options --- libraries/botbuilder-runtime/package.json | 1 + .../botbuilder-runtime/src/configuration.ts | 5 +- libraries/botbuilder-runtime/src/index.ts | 65 ++++++++++++++++--- .../test/configuration.test.ts | 2 +- .../botbuilder-runtime/test/index.test.ts | 50 +++++++++++++- 5 files changed, 109 insertions(+), 14 deletions(-) diff --git a/libraries/botbuilder-runtime/package.json b/libraries/botbuilder-runtime/package.json index 9e1e8d89b4..0a7ccfe078 100644 --- a/libraries/botbuilder-runtime/package.json +++ b/libraries/botbuilder-runtime/package.json @@ -31,6 +31,7 @@ "botbuilder": "4.1.6", "botbuilder-ai": "4.1.6", "botbuilder-applicationinsights": "4.1.6", + "botbuilder-azure": "4.1.6", "botbuilder-azure-blobs": "4.1.6", "botbuilder-dialogs": "4.1.6", "botbuilder-dialogs-adaptive": "4.1.6", diff --git a/libraries/botbuilder-runtime/src/configuration.ts b/libraries/botbuilder-runtime/src/configuration.ts index 5f7be9609f..23023d1c79 100644 --- a/libraries/botbuilder-runtime/src/configuration.ts +++ b/libraries/botbuilder-runtime/src/configuration.ts @@ -77,10 +77,11 @@ export class Configuration implements IConfiguration { /** * Load environment variables as a configuration source. * + * @param separator value used to indicate nesting * @returns this for chaining */ - env(): this { - this.provider.env(); + env(separator = '__'): this { + this.provider.env(separator); return this; } diff --git a/libraries/botbuilder-runtime/src/index.ts b/libraries/botbuilder-runtime/src/index.ts index 6803604f2b..4d16649585 100644 --- a/libraries/botbuilder-runtime/src/index.ts +++ b/libraries/botbuilder-runtime/src/index.ts @@ -10,10 +10,11 @@ import { ok } from 'assert'; import { AdaptiveComponentRegistration } from 'botbuilder-dialogs-adaptive'; import { ApplicationInsightsTelemetryClient, TelemetryInitializerMiddleware } from 'botbuilder-applicationinsights'; import { AuthenticationConfiguration, SimpleCredentialProvider } from 'botframework-connector'; -import { BlobsTranscriptStore } from 'botbuilder-azure-blobs'; +import { BlobsStorage, BlobsTranscriptStore } from 'botbuilder-azure-blobs'; import { ComponentRegistration } from 'botbuilder'; import { CoreBot } from './coreBot'; import { CoreBotAdapter } from './coreBotAdapter'; +import { CosmosDbPartitionedStorage } from 'botbuilder-azure'; import { IServices, ServiceCollection, TPlugin } from 'botbuilder-runtime-core'; import { LuisComponentRegistration, QnAMakerComponentRegistration } from 'botbuilder-ai'; import { ResourceExplorer } from 'botbuilder-dialogs-declarative'; @@ -46,15 +47,18 @@ function addFeatures(services: ServiceCollection, configuration: Conf } if (await configuration.bool(['traceTranscript'])) { - const [connectionString, container] = await Promise.all([ - configuration.string(['blobTranscript', 'connectionString']), - configuration.string(['blobTranscript', 'connectionString']), - ]); + const blobsTranscript = await configuration.type( + ['blobTranscript'], + t.Record({ + connectionString: t.String, + containerName: t.String, + }) + ); middlewareSet.use( new TranscriptLoggerMiddleware( - connectionString && container - ? new BlobsTranscriptStore(connectionString, container) + blobsTranscript + ? new BlobsTranscriptStore(blobsTranscript.connectionString, blobsTranscript.containerName) : new ConsoleTranscriptLogger() ) ); @@ -90,10 +94,51 @@ function addTelemetry(services: ServiceCollection, configuration: Con ); } -function addState(services: ServiceCollection): void { - services.addInstance('storage', new MemoryStorage()); +function addStorage(services: ServiceCollection, configuration: Configuration): void { services.addFactory('conversationState', ['storage'], ({ storage }) => new ConversationState(storage)); services.addFactory('userState', ['storage'], ({ storage }) => new UserState(storage)); + + services.addFactory('storage', async () => { + const storage = await configuration.string(['runtimeSettings', 'storage']); + + switch (storage) { + case 'BlobsStorage': { + const blobsStorage = await configuration.type( + ['BlobsStorage'], + t.Record({ + connectionString: t.String, + containerName: t.String, + }) + ); + + ok(blobsStorage); + + return new BlobsStorage(blobsStorage.connectionString, blobsStorage.containerName); + } + + case 'CosmosDbPartitionedStorage': { + const cosmosOptions = await configuration.type( + ['CosmosDbPartitionedStorage'], + t.Record({ + authKey: t.String.Or(t.Undefined), + compatibilityMode: t.Boolean.Or(t.Undefined), + containerId: t.String, + containerThroughput: t.Number.Or(t.Undefined), + cosmosDbEndpoint: t.String.Or(t.Undefined), + databaseId: t.String, + keySuffix: t.String.Or(t.Undefined), + }) + ); + + ok(cosmosOptions); + + return new CosmosDbPartitionedStorage(cosmosOptions); + } + + default: + return new MemoryStorage(); + } + }); } function addSkills(services: ServiceCollection, configuration: Configuration): void { @@ -298,7 +343,7 @@ export async function getRuntimeServices( addCoreBot(services, configuration); addFeatures(services, runtimeSettings.bind(['features'])); addSkills(services, runtimeSettings.bind(['skills'])); - addState(services); + addStorage(services, configuration); addTelemetry(services, runtimeSettings.bind(['telemetry'])); await addPlugins(services, configuration); diff --git a/libraries/botbuilder-runtime/test/configuration.test.ts b/libraries/botbuilder-runtime/test/configuration.test.ts index b4ed1a4bda..bae69dfbb4 100644 --- a/libraries/botbuilder-runtime/test/configuration.test.ts +++ b/libraries/botbuilder-runtime/test/configuration.test.ts @@ -13,7 +13,7 @@ describe('Configuration', () => { configuration.argv(['--strings.argv', 'argv']); - process.env['strings:env'] = 'env'; + process.env['strings__env'] = 'env'; configuration.env(); return configuration; diff --git a/libraries/botbuilder-runtime/test/index.test.ts b/libraries/botbuilder-runtime/test/index.test.ts index 8ed2c123f1..9337556aa5 100644 --- a/libraries/botbuilder-runtime/test/index.test.ts +++ b/libraries/botbuilder-runtime/test/index.test.ts @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { BotFrameworkAdapter } from 'botbuilder'; +import { BlobsStorage } from 'botbuilder-azure-blobs'; +import { BotFrameworkAdapter, MemoryStorage } from 'botbuilder'; import { Configuration, getRuntimeServices } from '../src'; +import { CosmosDbPartitionedStorage } from 'botbuilder-azure'; import { ok } from 'assert'; import { plugin } from 'botbuilder-runtime-core'; @@ -40,4 +42,50 @@ describe('getRuntimeServices', () => { const customAdapter = await services.mustMakeInstance('customAdapters'); ok(customAdapter.get('foo')); }); + + describe('storage', () => { + it('defaults to memory storage', async () => { + const [services] = await getRuntimeServices(__dirname, __dirname); + ok(services); + + const storage = await services.mustMakeInstance('storage'); + ok(storage instanceof MemoryStorage); + }); + + it('supports blobs storage', async () => { + const configuration = new Configuration().argv().env(); + + configuration.set(['runtimeSettings', 'storage'], 'BlobsStorage'); + + configuration.set(['BlobsStorage'], { + connectionString: 'UseDevelopmentStorage=true', + containerName: 'containerName', + }); + + const [services] = await getRuntimeServices(__dirname, configuration); + ok(services); + + const storage = await services.mustMakeInstance('storage'); + ok(storage instanceof BlobsStorage); + }); + + it('supports cosmos storage', async () => { + const configuration = new Configuration().argv().env(); + + configuration.set(['runtimeSettings', 'storage'], 'CosmosDbPartitionedStorage'); + + configuration.set(['CosmosDbPartitionedStorage'], { + authKey: 'authKey', + cosmosDbEndpoint: 'cosmosDbEndpoint', + containerId: 'containerId', + databaseId: 'databaseId', + }); + + const [services] = await getRuntimeServices(__dirname, configuration); + ok(services); + + const storage = await services.mustMakeInstance('storage'); + ok(storage instanceof CosmosDbPartitionedStorage); + }); + }); });