-
Notifications
You must be signed in to change notification settings - Fork 136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use Stacks API for Flex Consumption SKU #4104
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,32 +22,32 @@ import { getStorageConnectionString } from '../appSettings/connectionSettings/ge | |
import { enableFileLogging } from '../logstream/enableFileLogging'; | ||
import { type FullFunctionAppStack, type IFunctionAppWizardContext } from './IFunctionAppWizardContext'; | ||
import { showSiteCreated } from './showSiteCreated'; | ||
import { type FunctionAppRuntimeSettings } from './stacks/models/FunctionAppStackModel'; | ||
import { type FunctionAppRuntimeSettings, type Sku } from './stacks/models/FunctionAppStackModel'; | ||
|
||
export class FunctionAppCreateStep extends AzureWizardExecuteStep<IFunctionAppWizardContext> { | ||
public priority: number = 140; | ||
|
||
public async execute(context: IFunctionAppWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise<void> { | ||
const os: WebsiteOS = nonNullProp(context, 'newSiteOS'); | ||
const stack: FullFunctionAppStack | undefined = context.newSiteStack | ||
const stack: FullFunctionAppStack = nonNullProp(context, 'newSiteStack'); | ||
|
||
if (stack) { | ||
context.telemetry.properties.newSiteOS = os; | ||
context.telemetry.properties.newSiteStack = stack.stack.value; | ||
context.telemetry.properties.newSiteMajorVersion = stack.majorVersion.value; | ||
context.telemetry.properties.newSiteMinorVersion = stack.minorVersion.value; | ||
context.telemetry.properties.planSkuTier = context.plan?.sku?.tier; | ||
} | ||
context.telemetry.properties.newSiteOS = os; | ||
context.telemetry.properties.newSiteStack = stack.stack.value; | ||
context.telemetry.properties.newSiteMajorVersion = stack.majorVersion.value; | ||
context.telemetry.properties.newSiteMinorVersion = stack.minorVersion.value; | ||
context.telemetry.properties.planSkuTier = context.plan?.sku?.tier; | ||
|
||
const message: string = localize('creatingNewApp', 'Creating new function app "{0}"...', context.newSiteName); | ||
ext.outputChannel.appendLog(message); | ||
progress.report({ message }); | ||
|
||
const siteName: string = nonNullProp(context, 'newSiteName'); | ||
const rgName: string = nonNullProp(nonNullProp(context, 'resourceGroup'), 'name'); | ||
const flexSku: Sku | null | undefined = stack.minorVersion.stackSettings.linuxRuntimeSettings?.Sku && stack.minorVersion.stackSettings.linuxRuntimeSettings?.Sku[0]; | ||
|
||
// TODO: Because we don't have the stack API, assume no stack means it's a flex app | ||
context.site = stack ? await this.createFunctionApp(context, rgName, siteName, stack) : await this.createFlexFunctionApp(context, rgName, siteName); | ||
context.site = !flexSku ? | ||
await this.createFunctionApp(context, rgName, siteName, stack) : | ||
await this.createFlexFunctionApp(context, rgName, siteName, flexSku); | ||
context.activityResult = context.site as AppResource; | ||
|
||
const site = new ParsedSite(context.site, context); | ||
|
@@ -101,7 +101,7 @@ export class FunctionAppCreateStep extends AzureWizardExecuteStep<IFunctionAppWi | |
site.extendedLocation = { name: customLocation.id, type: 'customLocation' }; | ||
} | ||
|
||
private async getNewFlexSite(context: IFunctionAppWizardContext): Promise<Site> { | ||
private async getNewFlexSite(context: IFunctionAppWizardContext, sku: Sku): Promise<Site> { | ||
const location = await LocationListStep.getLocation(context, webProvider); | ||
const site: Site & { properties: FlexFunctionAppProperties } = { | ||
name: context.newSiteName, | ||
|
@@ -129,12 +129,12 @@ export class FunctionAppCreateStep extends AzureWizardExecuteStep<IFunctionAppWi | |
} | ||
}, | ||
runtime: { | ||
name: context.newSiteStackFlex?.runtime, | ||
version: context.newSiteStackFlex?.version | ||
name: sku.functionAppConfigProperties.runtime.name, | ||
version: sku.functionAppConfigProperties.runtime.version | ||
}, | ||
scaleAndConcurrency: { | ||
maximumInstanceCount: 100, | ||
instanceMemoryMB: 2048, | ||
maximumInstanceCount: sku.maximumInstanceCount.defaultValue, | ||
instanceMemoryMB: sku.instanceMemoryMB.find(im => im.isDefault)?.size || 2048, | ||
alwaysReady: [], | ||
triggers: null | ||
}, | ||
|
@@ -231,15 +231,15 @@ export class FunctionAppCreateStep extends AzureWizardExecuteStep<IFunctionAppWi | |
return await client.webApps.beginCreateOrUpdateAndWait(rgName, siteName, await this.getNewSite(context, stack)); | ||
} | ||
|
||
async createFlexFunctionApp(context: IFunctionAppWizardContext, rgName: string, siteName: string): Promise<Site> { | ||
async createFlexFunctionApp(context: IFunctionAppWizardContext, rgName: string, siteName: string, sku: Sku): Promise<Site> { | ||
const headers = createHttpHeaders({ | ||
'Content-Type': 'application/json', | ||
}); | ||
|
||
const options: AzExtRequestPrepareOptions = { | ||
url: `https://management.azure.com/subscriptions/${context.subscriptionId}/resourceGroups/${rgName}/providers/Microsoft.Web/sites/${siteName}?api-version=2023-12-01`, | ||
method: 'PUT', | ||
body: JSON.stringify(await this.getNewFlexSite(context)) as unknown as RequestBodyType, | ||
body: JSON.stringify(await this.getNewFlexSite(context, sku)) as unknown as RequestBodyType, | ||
Comment on lines
240
to
+242
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not for this PR, but this will eventually need to support sovereign clouds with different request urls |
||
headers | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ | |
|
||
import { type ServiceClient } from '@azure/core-client'; | ||
import { createPipelineRequest } from '@azure/core-rest-pipeline'; | ||
import { createGenericClient, type AzExtPipelineResponse } from '@microsoft/vscode-azext-azureutils'; | ||
import { createGenericClient, LocationListStep, type AzExtPipelineResponse } from '@microsoft/vscode-azext-azureutils'; | ||
import { openUrl, parseError, type AgentQuickPickItem, type IAzureQuickPickItem } from '@microsoft/vscode-azext-utils'; | ||
import { funcVersionLink } from '../../../FuncVersion'; | ||
import { hiddenStacksSetting, noRuntimeStacksAvailableLabel } from '../../../constants'; | ||
|
@@ -18,8 +18,10 @@ import { backupStacks } from './backupStacks'; | |
import { type AppStackMinorVersion } from './models/AppStackModel'; | ||
import { type FunctionAppRuntimes, type FunctionAppStack } from './models/FunctionAppStackModel'; | ||
|
||
export async function getStackPicks(context: IFunctionAppWizardContext): Promise<AgentQuickPickItem<IAzureQuickPickItem<FullFunctionAppStack | undefined>>[]> { | ||
const stacks: FunctionAppStack[] = (await getStacks(context)).filter(s => !context.stackFilter || context.stackFilter === s.value); | ||
export async function getStackPicks(context: IFunctionAppWizardContext, isFlex: boolean): Promise<AgentQuickPickItem<IAzureQuickPickItem<FullFunctionAppStack | undefined>>[]> { | ||
const stacks: FunctionAppStack[] = isFlex ? | ||
(await getFlexStacks(context)).filter(s => !context.stackFilter || context.stackFilter === s.value) : | ||
(await getStacks(context)).filter(s => !context.stackFilter || context.stackFilter === s.value); | ||
const picks: AgentQuickPickItem<IAzureQuickPickItem<FullFunctionAppStack | undefined>>[] = []; | ||
let hasEndOfLife = false; | ||
let stackHasPicks: boolean; | ||
|
@@ -176,6 +178,46 @@ async function getStacks(context: IFunctionAppWizardContext & { _stacks?: Functi | |
return context._stacks; | ||
} | ||
|
||
async function getFlexStacks(context: IFunctionAppWizardContext & { _stacks?: FunctionAppStack[] }): Promise<FunctionAppStack[]> { | ||
const client: ServiceClient = await createGenericClient(context, context); | ||
const location = await LocationListStep.getLocation(context); | ||
const flexFunctionAppStacks: FunctionAppStack[] = []; | ||
const stacks = ['dotnet', 'java', 'node', 'powershell', 'python']; | ||
if (!context._stacks) { | ||
const getFlexStack = async (stack: string) => { | ||
const result: AzExtPipelineResponse = await client.sendRequest(createPipelineRequest({ | ||
method: 'GET', | ||
url: requestUtils.createRequestUrl(`providers/Microsoft.Web/locations/${location.name}/functionAppStacks`, { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so flex stacks are location dependent? That's crazy There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I can see it being helpful for this team since they have to deploy skus to regions individually (it seems like). |
||
'api-version': '2023-12-01', | ||
stack, | ||
removeDeprecatedStacks: String(!getWorkspaceSetting<boolean>('showDeprecatedStacks')) | ||
}), | ||
})); | ||
const stacksArmResponse = <StacksArmResponse>result.parsedBody; | ||
for (const stack of stacksArmResponse.value) { | ||
stack.properties.majorVersions = stack.properties.majorVersions.filter(mv => { | ||
mv.minorVersions = mv.minorVersions.filter(minor => { | ||
// Remove stacks that don't have a SKU | ||
return minor.stackSettings.linuxRuntimeSettings && minor.stackSettings.linuxRuntimeSettings?.Sku !== null; | ||
|
||
}); | ||
|
||
return mv.minorVersions.length > 0; | ||
}); | ||
} | ||
flexFunctionAppStacks.push(...stacksArmResponse.value.map(d => d.properties)); | ||
} | ||
|
||
for (const stack of stacks) { | ||
await getFlexStack(stack); | ||
} | ||
|
||
context._stacks = flexFunctionAppStacks; | ||
} | ||
|
||
return context._stacks; | ||
} | ||
|
||
// API is still showing certain deprecated stacks even when 'removeDeprecatedStacks' queryParameter is set to true. | ||
// We should filter them out manually just in case. | ||
function removeDeprecatedStacks(stacks: FunctionAppStack[]) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer avoiding negating this and flipping the ternary around.