public async create()

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;
  }