Skip to content

Commit bbf572a

Browse files
committed
Start using Bicep and Az CLI for deployment
1 parent 45a4567 commit bbf572a

24 files changed

+989
-33
lines changed

Directory.Build.props

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))"
33
Condition="exists($([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../')))" />
44
<PropertyGroup>
5-
<ArtifactsDirectory Condition="'$(ArtifactsDirectory)' == ''">$(MSBuildThisFileDirectory)artifacts</ArtifactsDirectory>
6-
<SolutionName Condition="'$(SolutionName)' == ''">ExplorePackages</SolutionName>
7-
<_ProjectArtifactsDirectory>$(ArtifactsDirectory)\$(SolutionName)\$(MSBuildProjectName)\</_ProjectArtifactsDirectory>
5+
<ArtifactsSubdirectory Condition="'$(ArtifactsDirectory)' != ''">ExplorePackages\</ArtifactsSubdirectory>
6+
<ArtifactsSubdirectory Condition="'$(ArtifactsDirectory)' == ''"></ArtifactsSubdirectory>
7+
<ArtifactsDirectory Condition="'$(ArtifactsDirectory)' == ''">$(MSBuildThisFileDirectory)artifacts</ArtifactsDirectory>
8+
<DeployDirectory>$(ArtifactsDirectory)\deploy</DeployDirectory>
9+
<_ProjectArtifactsDirectory>$(ArtifactsDirectory)\$(ArtifactsSubdirectory)$(MSBuildProjectName)\</_ProjectArtifactsDirectory>
810
<BaseIntermediateOutputPath>$(_ProjectArtifactsDirectory)obj\</BaseIntermediateOutputPath>
911
<IntermediateOutputPath>$(BaseIntermediateOutputPath)$(Configuration)\</IntermediateOutputPath>
1012
<BaseOutputPath>$(_ProjectArtifactsDirectory)bin\</BaseOutputPath>

Directory.Build.targets

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Project>
2+
<Target Name="ZipPublishDir" AfterTargets="Publish">
3+
<ZipDirectory
4+
SourceDirectory="$(PublishDir)"
5+
DestinationFile="$(DeployDirectory)\$(MSBuildProjectName).zip"
6+
Overwrite="true" />
7+
</Target>
8+
</Project>

deploy/config/msdndev.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"Deployment": {
3+
"ExistingWebsitePlanId": "/subscriptions/87b3a2cf-ab4e-487b-a7af-59192cd21caf/resourceGroups/Shared-WestUS2/providers/Microsoft.Web/serverfarms/Shared-WestUS2",
4+
"WorkerCount": 3,
5+
"WorkerLogLevel": "Information"
6+
},
7+
"AppSettings": {
8+
"Shared": {
9+
"Knapcode.ExplorePackages": {
10+
"DisabledDrivers": [
11+
"NuGetPackageExplorerToCsv"
12+
],
13+
"RestrictUsers": false
14+
}
15+
},
16+
"Worker": {
17+
"Knapcode.ExplorePackages": {
18+
"MoveTempToHome": true
19+
}
20+
}
21+
}
22+
}

deploy/deploy.ps1

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[CmdletBinding()]
2+
param (
3+
[Parameter(Mandatory = $true)]
4+
[string]$ConfigName,
5+
6+
[Parameter(Mandatory)]
7+
[string]$StackName
8+
)
9+
10+
$ErrorActionPreference = "Stop"
11+
. (Join-Path $PSScriptRoot "scripts/common.ps1")
12+
13+
$configPath = Join-Path $PSScriptRoot "config/$ConfigName.json"
14+
function Get-Config() { Get-Content $configPath | ConvertFrom-Json | ConvertTo-Hashtable }
15+
16+
# Prepare the website config
17+
$websiteConfig = Get-Config
18+
$websiteConfig = Merge-Hashtable $websiteConfig.AppSettings.Shared $websiteConfig.AppSettings.Website
19+
20+
# Prepare the worker config
21+
$workerConfig = Get-Config
22+
$workerConfig = Merge-Hashtable $workerConfig.AppSettings.Shared $workerConfig.AppSettings.Worker
23+
24+
# Prepare the deployment parameters
25+
$parameters = Get-Config
26+
$parameters = $parameters.Deployment
27+
$parameters.StackName = if ($StackName) { $StackName } else {$ConfigName }
28+
$parameters.WebsiteConfig = $websiteConfig
29+
$parameters.WorkerConfig = $workerConfig
30+
31+
. (Join-Path $PSScriptRoot "scripts/Invoke-Deploy") @parameters

deploy/main.bicep

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// Parameters
2+
param stackName string
3+
4+
param storageAccountName string
5+
param storageKeySecretName string
6+
param sasDefinitionName string
7+
8+
param keyVaultName string
9+
10+
param websitePlanId string
11+
param websiteAadClientId string
12+
param websiteConfig array
13+
14+
param workerConfig array
15+
@allowed([
16+
'Warning'
17+
'Information'
18+
])
19+
param workerLogLevel string = 'Warning'
20+
@minValue(1)
21+
param workerCount int
22+
param existingWorkerCount int
23+
24+
// Shared resources
25+
resource insights 'Microsoft.Insights/components@2015-05-01' = {
26+
name: 'ExplorePackages-${stackName}'
27+
location: resourceGroup().location
28+
kind: 'web'
29+
properties: {
30+
Application_Type: 'web'
31+
}
32+
}
33+
34+
resource storageAccount 'Microsoft.Storage/storageAccounts@2019-06-01' existing = {
35+
name: storageAccountName
36+
}
37+
38+
// Cannot use a KeyVault reference for initial deployment.
39+
// https://github.com/Azure/azure-functions-host/issues/7094
40+
var storageSecretValue = 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};AccountKey=${listkeys(storageAccount.id, storageAccount.apiVersion).keys[0].value};EndpointSuffix=core.windows.net'
41+
var storageSecretReference = '@Microsoft.KeyVault(VaultName=${keyVaultName};SecretName=${storageKeySecretName})'
42+
var workerSecret = existingWorkerCount >= workerCount ? storageSecretReference : storageSecretValue
43+
44+
output needsAnotherDeploy bool = workerSecret != storageSecretReference
45+
output websiteDefaultHostName string = website.properties.defaultHostName
46+
output websiteHostNames array = website.properties.hostNames
47+
output websiteId string = website.id
48+
output workerIds array = [for i in range(0, workerCount): workers[i].id]
49+
50+
var sharedConfig = [
51+
{
52+
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
53+
value: insights.properties.InstrumentationKey
54+
}
55+
{
56+
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
57+
value: insights.properties.ConnectionString
58+
}
59+
{
60+
name: 'ApplicationInsightsAgent_EXTENSION_VERSION'
61+
value: '~2'
62+
}
63+
{
64+
name: 'Knapcode.ExplorePackages:StorageAccountName'
65+
value: storageAccountName
66+
}
67+
{
68+
name: 'Knapcode.ExplorePackages:StorageSharedAccessSignature'
69+
value: '@Microsoft.KeyVault(VaultName=${keyVaultName};SecretName=${storageAccountName}-${sasDefinitionName})'
70+
}
71+
{
72+
name: 'WEBSITE_RUN_FROM_PACKAGE'
73+
value: '1'
74+
}
75+
]
76+
77+
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
78+
name: keyVaultName
79+
location: resourceGroup().location
80+
properties: {
81+
tenantId: subscription().tenantId
82+
sku: {
83+
family: 'A'
84+
name: 'standard'
85+
}
86+
accessPolicies: [for i in range(0, workerCount + 1): {
87+
tenantId: i == 0 ? website.identity.tenantId : workers[i - 1].identity.tenantId
88+
objectId: i == 0 ? website.identity.principalId : workers[i - 1].identity.principalId
89+
permissions: {
90+
secrets: [
91+
'get'
92+
]
93+
}
94+
}]
95+
}
96+
}
97+
98+
resource keyVaultDiagnostics 'microsoft.insights/diagnosticSettings@2017-05-01-preview' = {
99+
scope: keyVault
100+
name: '${keyVaultName}-diagnostics'
101+
properties: {
102+
storageAccountId: storageAccount.id
103+
logs: [
104+
{
105+
category: 'AuditEvent'
106+
enabled: true
107+
retentionPolicy: {
108+
enabled: true
109+
days: 30
110+
}
111+
}
112+
]
113+
}
114+
}
115+
116+
// Website
117+
resource website 'Microsoft.Web/sites@2020-09-01' = {
118+
name: 'ExplorePackages-${stackName}'
119+
location: resourceGroup().location
120+
identity: {
121+
type: 'SystemAssigned'
122+
}
123+
properties: {
124+
serverFarmId: websitePlanId
125+
clientAffinityEnabled: false
126+
httpsOnly: true
127+
siteConfig: {
128+
webSocketsEnabled: true
129+
minTlsVersion: '1.2'
130+
netFrameworkVersion: 'v5.0'
131+
appSettings: concat([
132+
{
133+
name: 'AzureAd:Instance'
134+
value: 'https://login.microsoftonline.com/'
135+
}
136+
{
137+
name: 'AzureAd:ClientId'
138+
value: websiteAadClientId
139+
}
140+
{
141+
name: 'AzureAd:TenantId'
142+
value: 'common'
143+
}
144+
], sharedConfig, websiteConfig)
145+
}
146+
}
147+
}
148+
149+
// Workers
150+
resource workerPlan 'Microsoft.Web/serverfarms@2020-09-01' = {
151+
name: 'ExplorePackages-${stackName}-WorkerPlan'
152+
location: resourceGroup().location
153+
sku: {
154+
name: 'Y1'
155+
}
156+
}
157+
158+
resource workers 'Microsoft.Web/sites@2020-09-01' = [for i in range(0, workerCount): {
159+
name: 'ExplorePackages-${stackName}-Worker-${i}'
160+
location: resourceGroup().location
161+
kind: 'FunctionApp'
162+
identity: {
163+
type: 'SystemAssigned'
164+
}
165+
properties: {
166+
serverFarmId: workerPlan.id
167+
clientAffinityEnabled: false
168+
httpsOnly: true
169+
siteConfig: {
170+
minTlsVersion: '1.2'
171+
appSettings: concat([
172+
{
173+
name: 'AzureFunctionsJobHost__logging__LogLevel__Default'
174+
value: workerLogLevel
175+
}
176+
{
177+
name: 'AzureWebJobsFeatureFlags'
178+
value: 'EnableEnhancedScopes'
179+
}
180+
{
181+
name: 'AzureWebJobsStorage'
182+
value: workerSecret
183+
}
184+
{
185+
name: 'FUNCTIONS_EXTENSION_VERSION'
186+
value: '~3'
187+
}
188+
{
189+
name: 'FUNCTIONS_WORKER_RUNTIME'
190+
value: 'dotnet'
191+
}
192+
{
193+
name: 'SCM_DO_BUILD_DURING_DEPLOYMENT'
194+
value: 'false'
195+
}
196+
{
197+
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
198+
value: workerSecret
199+
}
200+
], sharedConfig, workerConfig)
201+
}
202+
}
203+
}]

deploy/scripts/Initialize-AadApp.ps1

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[CmdletBinding()]
2+
param (
3+
[Parameter(Mandatory = $true)]
4+
[string]$AadAppName
5+
)
6+
7+
$ErrorActionPreference = "Stop"
8+
. (Join-Path $PSScriptRoot "common.ps1")
9+
10+
Write-Status "Looking for AAD app with name '$AadAppName'..."
11+
Invoke-Call { az ad app list `
12+
--display-name $AadAppName `
13+
--query "[].{displayName: displayName, appId: appId, objectId: objectId}" } | Tee-Object -Variable 'existingApps' | Out-Host
14+
$existingApps = $existingApps | ConvertFrom-Json
15+
16+
if ($existingApps.Count -eq 0) {
17+
Write-Status "Creating a new AAD app..."
18+
Invoke-Call { az ad app create `
19+
--display-name $AadAppName `
20+
--query "{displayName: displayName, appId: appId, objectId: objectId}" } | Tee-Object -Variable 'app' | Out-Host
21+
$app = $app | ConvertFrom-Json
22+
Write-Status "Created new app with object ID '$($app.objectId)'."
23+
} elseif ($existingApps.Count -eq 1) {
24+
$app = $existingApps[0]
25+
Write-Status "Using existing app with object ID '$($app.objectId)'."
26+
} else {
27+
Write-Warning "There are $($existingApps.Count) apps with the name '$AadAppName'. Using the first with object ID '$($app.objectId)'."
28+
$app = $existingApps[0]
29+
}
30+
31+
New-Object PSObject -Property ([ordered]@{
32+
appId = $app.appId;
33+
objectId = $app.objectId
34+
})

0 commit comments

Comments
 (0)