Skip to content

Commit

Permalink
Start using Bicep and Az CLI for deployment
Browse files Browse the repository at this point in the history
  • Loading branch information
joelverhagen committed Apr 9, 2021
1 parent 45a4567 commit bbf572a
Show file tree
Hide file tree
Showing 24 changed files with 989 additions and 33 deletions.
8 changes: 5 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))"
Condition="exists($([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../')))" />
<PropertyGroup>
<ArtifactsDirectory Condition="'$(ArtifactsDirectory)' == ''">$(MSBuildThisFileDirectory)artifacts</ArtifactsDirectory>
<SolutionName Condition="'$(SolutionName)' == ''">ExplorePackages</SolutionName>
<_ProjectArtifactsDirectory>$(ArtifactsDirectory)\$(SolutionName)\$(MSBuildProjectName)\</_ProjectArtifactsDirectory>
<ArtifactsSubdirectory Condition="'$(ArtifactsDirectory)' != ''">ExplorePackages\</ArtifactsSubdirectory>
<ArtifactsSubdirectory Condition="'$(ArtifactsDirectory)' == ''"></ArtifactsSubdirectory>
<ArtifactsDirectory Condition="'$(ArtifactsDirectory)' == ''">$(MSBuildThisFileDirectory)artifacts</ArtifactsDirectory>
<DeployDirectory>$(ArtifactsDirectory)\deploy</DeployDirectory>
<_ProjectArtifactsDirectory>$(ArtifactsDirectory)\$(ArtifactsSubdirectory)$(MSBuildProjectName)\</_ProjectArtifactsDirectory>
<BaseIntermediateOutputPath>$(_ProjectArtifactsDirectory)obj\</BaseIntermediateOutputPath>
<IntermediateOutputPath>$(BaseIntermediateOutputPath)$(Configuration)\</IntermediateOutputPath>
<BaseOutputPath>$(_ProjectArtifactsDirectory)bin\</BaseOutputPath>
Expand Down
8 changes: 8 additions & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project>
<Target Name="ZipPublishDir" AfterTargets="Publish">
<ZipDirectory
SourceDirectory="$(PublishDir)"
DestinationFile="$(DeployDirectory)\$(MSBuildProjectName).zip"
Overwrite="true" />
</Target>
</Project>
22 changes: 22 additions & 0 deletions deploy/config/msdndev.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"Deployment": {
"ExistingWebsitePlanId": "/subscriptions/87b3a2cf-ab4e-487b-a7af-59192cd21caf/resourceGroups/Shared-WestUS2/providers/Microsoft.Web/serverfarms/Shared-WestUS2",
"WorkerCount": 3,
"WorkerLogLevel": "Information"
},
"AppSettings": {
"Shared": {
"Knapcode.ExplorePackages": {
"DisabledDrivers": [
"NuGetPackageExplorerToCsv"
],
"RestrictUsers": false
}
},
"Worker": {
"Knapcode.ExplorePackages": {
"MoveTempToHome": true
}
}
}
}
31 changes: 31 additions & 0 deletions deploy/deploy.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$ConfigName,

[Parameter(Mandatory)]
[string]$StackName
)

$ErrorActionPreference = "Stop"
. (Join-Path $PSScriptRoot "scripts/common.ps1")

$configPath = Join-Path $PSScriptRoot "config/$ConfigName.json"
function Get-Config() { Get-Content $configPath | ConvertFrom-Json | ConvertTo-Hashtable }

# Prepare the website config
$websiteConfig = Get-Config
$websiteConfig = Merge-Hashtable $websiteConfig.AppSettings.Shared $websiteConfig.AppSettings.Website

# Prepare the worker config
$workerConfig = Get-Config
$workerConfig = Merge-Hashtable $workerConfig.AppSettings.Shared $workerConfig.AppSettings.Worker

# Prepare the deployment parameters
$parameters = Get-Config
$parameters = $parameters.Deployment
$parameters.StackName = if ($StackName) { $StackName } else {$ConfigName }
$parameters.WebsiteConfig = $websiteConfig
$parameters.WorkerConfig = $workerConfig

. (Join-Path $PSScriptRoot "scripts/Invoke-Deploy") @parameters
203 changes: 203 additions & 0 deletions deploy/main.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// Parameters
param stackName string

param storageAccountName string
param storageKeySecretName string
param sasDefinitionName string

param keyVaultName string

param websitePlanId string
param websiteAadClientId string
param websiteConfig array

param workerConfig array
@allowed([
'Warning'
'Information'
])
param workerLogLevel string = 'Warning'
@minValue(1)
param workerCount int
param existingWorkerCount int

// Shared resources
resource insights 'Microsoft.Insights/components@2015-05-01' = {
name: 'ExplorePackages-${stackName}'
location: resourceGroup().location
kind: 'web'
properties: {
Application_Type: 'web'
}
}

resource storageAccount 'Microsoft.Storage/storageAccounts@2019-06-01' existing = {
name: storageAccountName
}

// Cannot use a KeyVault reference for initial deployment.
// https://github.com/Azure/azure-functions-host/issues/7094
var storageSecretValue = 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};AccountKey=${listkeys(storageAccount.id, storageAccount.apiVersion).keys[0].value};EndpointSuffix=core.windows.net'
var storageSecretReference = '@Microsoft.KeyVault(VaultName=${keyVaultName};SecretName=${storageKeySecretName})'
var workerSecret = existingWorkerCount >= workerCount ? storageSecretReference : storageSecretValue

output needsAnotherDeploy bool = workerSecret != storageSecretReference
output websiteDefaultHostName string = website.properties.defaultHostName
output websiteHostNames array = website.properties.hostNames
output websiteId string = website.id
output workerIds array = [for i in range(0, workerCount): workers[i].id]

var sharedConfig = [
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: insights.properties.InstrumentationKey
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: insights.properties.ConnectionString
}
{
name: 'ApplicationInsightsAgent_EXTENSION_VERSION'
value: '~2'
}
{
name: 'Knapcode.ExplorePackages:StorageAccountName'
value: storageAccountName
}
{
name: 'Knapcode.ExplorePackages:StorageSharedAccessSignature'
value: '@Microsoft.KeyVault(VaultName=${keyVaultName};SecretName=${storageAccountName}-${sasDefinitionName})'
}
{
name: 'WEBSITE_RUN_FROM_PACKAGE'
value: '1'
}
]

resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: keyVaultName
location: resourceGroup().location
properties: {
tenantId: subscription().tenantId
sku: {
family: 'A'
name: 'standard'
}
accessPolicies: [for i in range(0, workerCount + 1): {
tenantId: i == 0 ? website.identity.tenantId : workers[i - 1].identity.tenantId
objectId: i == 0 ? website.identity.principalId : workers[i - 1].identity.principalId
permissions: {
secrets: [
'get'
]
}
}]
}
}

resource keyVaultDiagnostics 'microsoft.insights/diagnosticSettings@2017-05-01-preview' = {
scope: keyVault
name: '${keyVaultName}-diagnostics'
properties: {
storageAccountId: storageAccount.id
logs: [
{
category: 'AuditEvent'
enabled: true
retentionPolicy: {
enabled: true
days: 30
}
}
]
}
}

// Website
resource website 'Microsoft.Web/sites@2020-09-01' = {
name: 'ExplorePackages-${stackName}'
location: resourceGroup().location
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: websitePlanId
clientAffinityEnabled: false
httpsOnly: true
siteConfig: {
webSocketsEnabled: true
minTlsVersion: '1.2'
netFrameworkVersion: 'v5.0'
appSettings: concat([
{
name: 'AzureAd:Instance'
value: 'https://login.microsoftonline.com/'
}
{
name: 'AzureAd:ClientId'
value: websiteAadClientId
}
{
name: 'AzureAd:TenantId'
value: 'common'
}
], sharedConfig, websiteConfig)
}
}
}

// Workers
resource workerPlan 'Microsoft.Web/serverfarms@2020-09-01' = {
name: 'ExplorePackages-${stackName}-WorkerPlan'
location: resourceGroup().location
sku: {
name: 'Y1'
}
}

resource workers 'Microsoft.Web/sites@2020-09-01' = [for i in range(0, workerCount): {
name: 'ExplorePackages-${stackName}-Worker-${i}'
location: resourceGroup().location
kind: 'FunctionApp'
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: workerPlan.id
clientAffinityEnabled: false
httpsOnly: true
siteConfig: {
minTlsVersion: '1.2'
appSettings: concat([
{
name: 'AzureFunctionsJobHost__logging__LogLevel__Default'
value: workerLogLevel
}
{
name: 'AzureWebJobsFeatureFlags'
value: 'EnableEnhancedScopes'
}
{
name: 'AzureWebJobsStorage'
value: workerSecret
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~3'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'dotnet'
}
{
name: 'SCM_DO_BUILD_DURING_DEPLOYMENT'
value: 'false'
}
{
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
value: workerSecret
}
], sharedConfig, workerConfig)
}
}
}]
34 changes: 34 additions & 0 deletions deploy/scripts/Initialize-AadApp.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$AadAppName
)

$ErrorActionPreference = "Stop"
. (Join-Path $PSScriptRoot "common.ps1")

Write-Status "Looking for AAD app with name '$AadAppName'..."
Invoke-Call { az ad app list `
--display-name $AadAppName `
--query "[].{displayName: displayName, appId: appId, objectId: objectId}" } | Tee-Object -Variable 'existingApps' | Out-Host
$existingApps = $existingApps | ConvertFrom-Json

if ($existingApps.Count -eq 0) {
Write-Status "Creating a new AAD app..."
Invoke-Call { az ad app create `
--display-name $AadAppName `
--query "{displayName: displayName, appId: appId, objectId: objectId}" } | Tee-Object -Variable 'app' | Out-Host
$app = $app | ConvertFrom-Json
Write-Status "Created new app with object ID '$($app.objectId)'."
} elseif ($existingApps.Count -eq 1) {
$app = $existingApps[0]
Write-Status "Using existing app with object ID '$($app.objectId)'."
} else {
Write-Warning "There are $($existingApps.Count) apps with the name '$AadAppName'. Using the first with object ID '$($app.objectId)'."
$app = $existingApps[0]
}

New-Object PSObject -Property ([ordered]@{
appId = $app.appId;
objectId = $app.objectId
})
Loading

0 comments on commit bbf572a

Please sign in to comment.