in extensions/azurePublish/src/node/provision.ts [233:453]
public async create(config: ProvisionConfig) {
// this object collects all of the various configuration output
const provisionResults = {
success: false,
provisionedCount: 0,
errorMessage: null,
subscriptionId: null,
appId: null,
appPassword: null,
resourceGroup: null,
webApp: null,
luisPrediction: null,
luisAuthoring: null,
blobStorage: null,
cosmosDB: null,
appInsights: null,
qna: null,
botName: null,
tenantId: this.tenantId,
};
try {
// ensure a tenantId is available.
if (!this.tenantId) {
this.tenantId = config.tenantId ?? (await this.getTenantId());
provisionResults.tenantId = this.tenantId;
}
// tokenCredentials is used for authentication across the API calls
const tokenCredentials = new TokenCredentials(this.accessToken);
const resourceGroupName = config.resourceGroup ?? config.hostname;
// azure resource manager class config
const armConfig = {
subscriptionId: this.subscriptionId,
creds: tokenCredentials,
logger: this.logger,
} as AzureResourceManangerConfig;
provisionResults.subscriptionId = this.subscriptionId;
// This object is used to actually make the calls to Azure...
this.azureResourceManagementClient = new AzureResourceMananger(armConfig);
// Ensure the resource group is ready
provisionResults.resourceGroup = await this.azureResourceManagementClient.createResourceGroup({
name: resourceGroupName,
location: config.location,
});
// SOME OF THESE MUST HAPPEN IN THE RIGHT ORDER!
// TODO: ensure the sort order?
// app reg first (get app id)
// then bot webapp (get endpoint)
// then bot reg (use app id and endpoint)
// Loop over the list of required resources and take the actions necessary to create them.
for (let x = 0; x < config.externalResources.length; x++) {
const resourceToCreate = config.externalResources[x];
switch (resourceToCreate.key) {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
// Create the appId and appPassword - this is usually the first step.
case AzureResourceTypes.APP_REGISTRATION:
// eslint-disable-next-line no-case-declarations
const { appId, appPassword } = await this.createApp(config.hostname);
provisionResults.appId = appId;
provisionResults.appPassword = appPassword;
break;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
// Create a web app to host the bot
case AzureResourceTypes.WEBAPP:
// eslint-disable-next-line no-case-declarations
const hostname = await this.azureResourceManagementClient.deployWebAppResource({
resourceGroupName: resourceGroupName,
location: config.location ?? provisionResults.resourceGroup.location,
name: config.hostname,
operatingSystem: config.appServiceOperatingSystem,
});
provisionResults.webApp = {
hostname: hostname,
};
break;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
// Create the Azure Bot Service registration
case AzureResourceTypes.BOT_REGISTRATION: {
const botName = await this.azureResourceManagementClient.deployBotResource({
resourceGroupName: resourceGroupName,
location: config.location ?? provisionResults.resourceGroup.location,
name: config.hostname, // come back to this!
displayName: config.hostname, // todo: this may be wrong!
endpoint: `https://${
provisionResults.webApp?.hostname ?? config.hostname + '.azurewebsites.net'
}/api/messages`,
appId: provisionResults.appId,
webAppHostname: provisionResults.webApp.hostname,
});
provisionResults.botName = botName;
break;
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
// Create the Azure Bot Service registration
case AzureResourceTypes.AZUREFUNCTIONS: {
const functionsHostName = await this.azureResourceManagementClient.deployAzureFunctions({
resourceGroupName: resourceGroupName,
location: config.location ?? provisionResults.resourceGroup.location,
name: config.hostname,
workerRuntime: config.workerRuntime,
appId: provisionResults.appId,
appPwd: provisionResults.appPassword,
operatingSystem: config.appServiceOperatingSystem,
});
provisionResults.webApp = {
hostname: functionsHostName,
};
break;
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
// Create the Cosmo DB for state
case AzureResourceTypes.COSMOSDB:
provisionResults.cosmosDB = await this.azureResourceManagementClient.deployCosmosDBResource({
resourceGroupName: resourceGroupName,
location: config.location ?? provisionResults.resourceGroup.location,
name: config.hostname.replace(/_/g, '').substr(0, 31).toLowerCase(),
databaseName: `botstate-db`,
containerName: `botstate-container`,
});
break;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
// Create the app insights for telemetry
case AzureResourceTypes.APPINSIGHTS:
provisionResults.appInsights = await this.azureResourceManagementClient.deployAppInsightsResource({
resourceGroupName: resourceGroupName,
location: config.location ?? provisionResults.resourceGroup.location,
name: config.hostname,
});
// connect the new app insights stuff to the bot registration (if created)
if (config.externalResources.find((r) => r.key === AzureResourceTypes.BOT_REGISTRATION)) {
await this.azureResourceManagementClient.connectAppInsightsToBotService({
resourceGroupName: resourceGroupName,
name: config.hostname,
});
}
break;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
// Create a LUIS authoring key
case AzureResourceTypes.LUIS_AUTHORING:
provisionResults.luisAuthoring = await this.azureResourceManagementClient.deployLuisAuthoringResource({
resourceGroupName: resourceGroupName,
location: config.luisLocation,
name: `${config.hostname}-luis-authoring`,
});
break;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
// Create a LUIS prediction key
case AzureResourceTypes.LUIS_PREDICTION:
// eslint-disable-next-line no-case-declarations
provisionResults.luisPrediction = await this.azureResourceManagementClient.deployLuisResource({
resourceGroupName: resourceGroupName,
location: config.luisLocation,
name: `${config.hostname}-luis`,
});
break;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
// Create a blob storage for transcripts
case AzureResourceTypes.BLOBSTORAGE:
// eslint-disable-next-line no-case-declarations
provisionResults.blobStorage = await this.azureResourceManagementClient.deployBlobStorageResource({
resourceGroupName: resourceGroupName,
location: config.location ?? provisionResults.resourceGroup.location,
name: config.hostname.toLowerCase().replace(/-/g, '').replace(/_/g, ''),
containerName: 'transcripts',
});
break;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
// Create qna account and related search resources
case AzureResourceTypes.QNA:
provisionResults.qna = await this.azureResourceManagementClient.deployQnAReource({
resourceGroupName: resourceGroupName,
location: config.location ?? provisionResults.resourceGroup.location,
name: config.hostname,
});
break;
default:
continue;
}
provisionResults.provisionedCount += 1;
}
provisionResults.success = true;
// TODO: NOT SURE WHAT THIS DOES! Something about tracking what deployments happen because of composer?
// await this.azureResourceManagementClient.deployDeploymentCounter({
// resourceGroupName: resourceGroupName,
// name: '1d41002f-62a1-49f3-bd43-2f3f32a19cbb', // WHAT IS THIS CONSTANT???
// });
} catch (err) {
const errorMessage = JSON.stringify(err, Object.getOwnPropertyNames(err));
this.logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: errorMessage,
});
provisionResults.errorMessage = errorMessage;
}
return provisionResults;
}