Skip to content

Commit

Permalink
feat: implement provisioning for app registration, web app, service p…
Browse files Browse the repository at this point in the history
…lan (#8201)

* implement provisioning for app registration, web app, service plan

* remove app registration dependency from app insights, qna, service plan

* add canPollStatus attribute to provision services.

Co-authored-by: natalgar <natalie.garcia@microsoft.com>
  • Loading branch information
natalgar and natalgar committed Jun 29, 2021
1 parent 2088670 commit a4770e5
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 91 deletions.
15 changes: 6 additions & 9 deletions extensions/azurePublishNew/src/node/availableResources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const getProvisionServices = (config: ProvisionConfig): Record<string, Re
return {
appRegistration: getAppRegistrationProvisionService(config),
webApp: getWebAppProvisionService(config),
servicePlan: getAppServiceProvisionService(config),
botRegistration: getBotChannelProvisionService(),
azureFunctionApp: getAzureFunctionsProvisionService(),
cosmosDB: getCosmosDbProvisionService(),
Expand All @@ -40,23 +41,19 @@ export const getProvisionServices = (config: ProvisionConfig): Record<string, Re
luisPrediction: getLuisPredictionProvisionService(),
blobStorage: getBlogStorageProvisionService(),
qna: getQnAProvisionService(),
servicePlan: getAppServiceProvisionService(),
};
};

export const setUpProvisionService = (config: ProvisionConfig) => {
const provisionServices = getProvisionServices(config);

const provision = (): void => {
const selectedResources: ResourceConfig[] = [];
const provision = (selectedResources: ResourceConfig[]): void => {
let workingSet: Record<string, object> = {};

const provisionServices = getProvisionServices(config);

const workingSet: Record<string, object> = {};
selectedResources.forEach((resourceConfig) => {
const service = provisionServices[resourceConfig.key];
selectedResources.forEach(async (resourceConfig) => {
const service: ResourceProvisionService = provisionServices[resourceConfig.key];
if (service) {
service.provision(resourceConfig, workingSet);
workingSet = await service.provision(resourceConfig, workingSet);
}
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ const getAppInsightsProvisionMethod = (): ProvisionMethod => {

export const getAppInsightsProvisionService = (): ResourceProvisionService => {
return {
getDependencies: () => ['appRegistration', 'botRegistration'],
getDependencies: () => ['botRegistration'],
getRecommendationForProject: (project) => 'optional',
provision: getAppInsightsProvisionMethod(),
canPollStatus: false,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,20 @@ import { AxiosRequestConfig } from 'axios';
import * as rp from 'request-promise';

import { createCustomizeError, ProvisionErrors } from '../../../../azurePublish/src/node/utils/errorHandler';
import { ProvisionConfig, ProvisionWorkingSet, ResourceDefinition, ResourceProvisionService } from '../types';

import { FREE_APP_REGISTRATION_TIER, AZURE_HOSTING_GROUP_NAME } from './constants';
import { AppRegistrationResult } from './types';
import {
ProvisionConfig,
ProvisionWorkingSet,
ResourceConfig,
ResourceDefinition,
ResourceProvisionService,
} from '../types';

import { AZURE_HOSTING_GROUP_NAME, FREE_APP_REGISTRATION_TIER } from './constants';

type AppRegistrationResourceConfig = ResourceConfig & {
key: 'appRegistration';
appName: string;
};

export const appRegistrationDefinition: ResourceDefinition = {
key: 'appRegistration',
Expand Down Expand Up @@ -48,11 +58,8 @@ const postRequestWithRetry = async (requestUri: string, requestOptions: AxiosReq
return result;
};

const appRegistrationProvisionMethod = async (
config: ProvisionConfig,
workingSet: ProvisionWorkingSet
): Promise<ProvisionWorkingSet> => {
const { graphToken } = config.credentials;
const appRegistrationProvisionMethod = (provisionConfig: ProvisionConfig) => {
const { graphToken } = provisionConfig;
const requestOptions: rp.RequestPromiseOptions = {
json: true,
headers: { Authorization: `Bearer ${graphToken}` },
Expand Down Expand Up @@ -82,15 +89,18 @@ const appRegistrationProvisionMethod = async (
return passwordSet.secretText;
};

const { webAppName } = config;
const { appId, id } = await createApp(webAppName);
const appPassword = await addPassword(webAppName, id);

const provisionResult: AppRegistrationResult = { key: 'appRegistration', appId, appPassword };

return {
...workingSet,
appRegistration: provisionResult,
return async (
resourceConfig: AppRegistrationResourceConfig,
workingSet: ProvisionWorkingSet
): Promise<ProvisionWorkingSet> => {
const { appName } = resourceConfig;
const { appId, id } = await createApp(appName);
const appPassword = await addPassword(appName, id);

return {
...workingSet,
appRegistration: { appId, appPassword },
};
};
};

Expand All @@ -99,6 +109,7 @@ export const getAppRegistrationProvisionService = (config: ProvisionConfig): Res
getDependencies: () => [],
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getRecommendationForProject: (project) => 'required',
provision: appRegistrationProvisionMethod,
provision: appRegistrationProvisionMethod(config),
canPollStatus: false,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ export const getAzureFunctionsProvisionService = (): ResourceProvisionService =>
return runtimeType === 'functions' ? 'required' : 'notAllowed';
},
provision: getAzureFunctionsProvisionMethod(),
canPollStatus: false,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ export const getBlogStorageProvisionService = (): ResourceProvisionService => {
getDependencies: () => [],
getRecommendationForProject: (project) => 'optional',
provision: getBlobStorageProvisionMethod(),
canPollStatus: false,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ export const getBotChannelProvisionService = (): ResourceProvisionService => {
getDependencies: () => ['appRegistration', 'webApp'],
getRecommendationForProject: (project) => 'required',
provision: getBotChannelProvisionMethod(),
canPollStatus: false,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ export const getCosmosDbProvisionService = (): ResourceProvisionService => {
getDependencies: () => [],
getRecommendationForProject: (project) => 'optional',
provision: getCosmosDbProvisionMethod(),
canPollStatus: false,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ export const getLuisAuthoringProvisionService = (): ResourceProvisionService =>
return project.requiresLuisAuthoring; // tbd
},
provision: getLuisAuthoringProvisionMethod(),
canPollStatus: false,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ export const getLuisPredictionProvisionService = (): ResourceProvisionService =>
return project.requiresLuisPrediction; // tbd
},
provision: getLuisPredictionProvisionMethod(),
canPollStatus: false,
};
};
3 changes: 2 additions & 1 deletion extensions/azurePublishNew/src/node/azureResources/qna.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ const getQnAProvisionMethod = (): ProvisionMethod => {

export const getQnAProvisionService = (): ResourceProvisionService => {
return {
getDependencies: () => ['appRegistration', 'webApp'],
getDependencies: () => ['webApp'],
getRecommendationForProject: (project) => {
return project.isQnARequired; // tbd
},
provision: getQnAProvisionMethod(),
canPollStatus: false,
};
};
67 changes: 54 additions & 13 deletions extensions/azurePublishNew/src/node/azureResources/servicePlan.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { TokenCredentials } from '@azure/ms-rest-js';
import { WebSiteManagementClient } from '@azure/arm-appservice';

import { parseRuntimeKey } from '../../../../../Composer/packages/lib/shared';
import { ProvisionMethod, ProvisionWorkingSet, ResourceDefinition, ResourceProvisionService } from '../types';
import {
ProvisionConfig,
ProvisionWorkingSet,
ResourceConfig,
ResourceDefinition,
ResourceProvisionService,
} from '../types';
import { createCustomizeError, ProvisionErrors } from '../../../../azurePublish/src/node/utils/errorHandler';

import { AZURE_HOSTING_GROUP_NAME, S1_STANDARD_TIER } from './constants';

import { S1_STANDARD_TIER, AZURE_HOSTING_GROUP_NAME } from './constants';
import { AppServiceConfig } from './types';
type ServicePlanConfig = ResourceConfig & {
key: 'servicePlan';
resourceGroupName: string;
appServicePlanName: string;
appServicePlanOptions: {}; // has location, operationsystem
location: string;
operatingSystem: string;
};

export const servicePlanDefinition: ResourceDefinition = {
key: 'servicePlan',
Expand All @@ -16,24 +34,47 @@ export const servicePlanDefinition: ResourceDefinition = {
group: AZURE_HOSTING_GROUP_NAME,
};

const getAppServiceProvisionMethod = (): ProvisionMethod => {
return (config: AppServiceConfig, workingSet: ProvisionWorkingSet): Promise<ProvisionWorkingSet> => {
const provisionResult = {};
const appServiceProvisionMethod = (provisionConfig: ProvisionConfig) => {
const tokenCredentials = new TokenCredentials(provisionConfig.accessToken);
const webSiteManagementClient = new WebSiteManagementClient(tokenCredentials, provisionConfig.subscriptionId);

return {
...workingSet,
appService: provisionResult,
};
return async (resourceConfig: ServicePlanConfig, workingSet: ProvisionWorkingSet): Promise<ProvisionWorkingSet> => {
const operatingSystem = resourceConfig.operatingSystem ? resourceConfig.operatingSystem : 'windows';
try {
// Create new Service Plan
const appServiceResult = await webSiteManagementClient.appServicePlans.createOrUpdate(
resourceConfig.resourceGroupName,
resourceConfig.appServicePlanName,
{
location: resourceConfig.location,
kind: operatingSystem,
reserved: operatingSystem === 'linux',
sku: {
name: 'S1',
tier: 'Standard',
size: 'S1',
family: 'S',
capacity: 1,
},
}
);
return { ...workingSet, appService: { appServicePlanName: appServiceResult.name } };
} catch (err) {
if (err.status >= 300) {
throw createCustomizeError(ProvisionErrors.CREATE_WEB_APP_ERROR, err.message);
}
}
};
};

export const getAppServiceProvisionService = (): ResourceProvisionService => {
export const getAppServiceProvisionService = (config: ProvisionConfig): ResourceProvisionService => {
return {
getDependencies: () => ['appRegistration'],
getDependencies: () => [],
getRecommendationForProject: (project) => {
const { runtimeType } = parseRuntimeKey(project.settings?.runtime?.key);
return runtimeType !== 'functions' ? 'required' : 'notAllowed';
},
provision: getAppServiceProvisionMethod(),
provision: appServiceProvisionMethod(config),
canPollStatus: true,
};
};
92 changes: 54 additions & 38 deletions extensions/azurePublishNew/src/node/azureResources/webApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,18 @@ import { WebSiteManagementClient } from '@azure/arm-appservice';
import { TokenCredentials } from '@azure/ms-rest-js';
import { parseRuntimeKey } from '@bfc/shared';

import { ProvisionConfig, ProvisionWorkingSet, ResourceDefinition, ResourceProvisionService } from '../types';
import {
ProvisionConfig,
ProvisionWorkingSet,
ResourceConfig,
ResourceDefinition,
ResourceProvisionService,
} from '../types';
import {
createCustomizeError,
ProvisionErrors,
stringifyError,
} from '../../../../azurePublish/src/node/utils/errorHandler';

import { AZURE_HOSTING_GROUP_NAME, S1_STANDARD_TIER } from './constants';

Expand All @@ -18,54 +29,59 @@ export const webAppResourceDefinition: ResourceDefinition = {
group: AZURE_HOSTING_GROUP_NAME,
};

const createWebApp = async (client: WebSiteManagementClient, config: ProvisionConfig) => {
const { resourceGroupName, webAppName, serverFarm, location } = config;
return await client.webApps.createOrUpdate(resourceGroupName, webAppName, {
name: webAppName,
serverFarmId: serverFarm,
location: location,
kind: 'app',
siteConfig: {
webSocketsEnabled: true,
appSettings: [
{
name: 'WEBSITE_NODE_DEFAULT_VERSION',
value: '10.14.1',
},
],
cors: {
allowedOrigins: ['https://botservice.hosting.portal.azure.net', 'https://hosting.onecloud.azure-test.net/'],
},
},
});
type WebAppResourceConfig = ResourceConfig & {
key: 'webApp';
resourceGroupName: string;
location: string;
webAppName: string;
appServicePlanName: string;
operatingSystem: string;
};

const webAppProvisionMethod = async (
client: WebSiteManagementClient,
config: ProvisionConfig,
workingSet: {}
): Promise<ProvisionWorkingSet> => {
// const appRegistrationResult = workingSet.appRegistration;
const webAppResult = await createWebApp(client, config);
const hostname = webAppResult?.hostNames?.[0];
const webAppProvisionMethod = (provisionConfig: ProvisionConfig) => {
const tokenCredentials = new TokenCredentials(provisionConfig.accessToken);
const webSiteManagementClient = new WebSiteManagementClient(tokenCredentials, provisionConfig.subscriptionId);

const result = { hostname: hostname };
return {
...workingSet,
webAppResult: result,
return async (
resourceConfig: WebAppResourceConfig,
workingSet: ProvisionWorkingSet
): Promise<ProvisionWorkingSet> => {
const { resourceGroupName, webAppName, operatingSystem, location } = resourceConfig;
try {
const webAppResult = await webSiteManagementClient.webApps.createOrUpdate(resourceGroupName, webAppName, {
name: webAppName,
serverFarmId: workingSet.appService.appServicePlanName,
location: location,
kind: operatingSystem === 'linux' ? 'app,linux' : 'app',
siteConfig: {
webSocketsEnabled: true,
appSettings: [
{
name: 'WEBSITE_NODE_DEFAULT_VERSION',
value: '10.14.1',
},
],
cors: {
allowedOrigins: ['https://botservice.hosting.portal.azure.net', 'https://hosting.onecloud.azure-test.net/'],
},
},
});
const hostname = webAppResult?.hostNames?.[0];
return { ...workingSet, webAppResult: { hostname: hostname } };
} catch (err) {
throw createCustomizeError(ProvisionErrors.CREATE_WEB_APP_ERROR, stringifyError(err));
}
};
};

export const getWebAppProvisionService = (config: ProvisionConfig): ResourceProvisionService => {
const tokenCredentials = new TokenCredentials(config.credentials.token);
const webSiteManagementClient = new WebSiteManagementClient(tokenCredentials, config.subscriptionId);

return {
getDependencies: () => ['appRegistration', 'servicePlan'],
getDependencies: () => ['servicePlan'],
getRecommendationForProject: (project) => {
const { runtimeType } = parseRuntimeKey(project.settings?.runtime?.key);
return runtimeType !== 'functions' ? 'required' : 'notAllowed';
},
provision: () => webAppProvisionMethod(webSiteManagementClient, config, {}),
provision: webAppProvisionMethod(config),
canPollStatus: true,
};
};

0 comments on commit a4770e5

Please sign in to comment.