├── Deploy-AzureResourceGroup.ps1 ├── Deployment.targets ├── README.md ├── azuredeploy.json ├── azuredeploy.parameters.json └── resource-group-template.deployproj /Deploy-AzureResourceGroup.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 2 | #Requires -Module AzureRM.Resources 3 | #Requires -Module Azure.Storage 4 | 5 | Param( 6 | [string] [Parameter(Mandatory=$true)] $ResourceGroupLocation, 7 | [string] $ResourceGroupName = 'csv-to-email', 8 | [switch] $UploadArtifacts, 9 | [string] $StorageAccountName, 10 | [string] $StorageContainerName = $ResourceGroupName.ToLowerInvariant() + '-stageartifacts', 11 | [string] $TemplateFile = 'LogicApp.json', 12 | [string] $TemplateParametersFile = 'LogicApp.parameters.json', 13 | [string] $ArtifactStagingDirectory = '.', 14 | [string] $DSCSourceFolder = 'DSC', 15 | [switch] $ValidateOnly 16 | ) 17 | 18 | try { 19 | [Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent("VSAzureTools-$UI$($host.name)".replace(' ','_'), '2.9.6') 20 | } catch { } 21 | 22 | $ErrorActionPreference = 'Stop' 23 | Set-StrictMode -Version 3 24 | 25 | function Format-ValidationOutput { 26 | param ($ValidationOutput, [int] $Depth = 0) 27 | Set-StrictMode -Off 28 | return @($ValidationOutput | Where-Object { $_ -ne $null } | ForEach-Object { @(' ' * $Depth + ': ' + $_.Message) + @(Format-ValidationOutput @($_.Details) ($Depth + 1)) }) 29 | } 30 | 31 | $OptionalParameters = New-Object -TypeName Hashtable 32 | $TemplateFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateFile)) 33 | $TemplateParametersFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateParametersFile)) 34 | 35 | if ($UploadArtifacts) { 36 | # Convert relative paths to absolute paths if needed 37 | $ArtifactStagingDirectory = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $ArtifactStagingDirectory)) 38 | $DSCSourceFolder = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $DSCSourceFolder)) 39 | 40 | # Parse the parameter file and update the values of artifacts location and artifacts location SAS token if they are present 41 | $JsonParameters = Get-Content $TemplateParametersFile -Raw | ConvertFrom-Json 42 | if (($JsonParameters | Get-Member -Type NoteProperty 'parameters') -ne $null) { 43 | $JsonParameters = $JsonParameters.parameters 44 | } 45 | $ArtifactsLocationName = '_artifactsLocation' 46 | $ArtifactsLocationSasTokenName = '_artifactsLocationSasToken' 47 | $OptionalParameters[$ArtifactsLocationName] = $JsonParameters | Select -Expand $ArtifactsLocationName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore 48 | $OptionalParameters[$ArtifactsLocationSasTokenName] = $JsonParameters | Select -Expand $ArtifactsLocationSasTokenName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore 49 | 50 | # Create DSC configuration archive 51 | if (Test-Path $DSCSourceFolder) { 52 | $DSCSourceFilePaths = @(Get-ChildItem $DSCSourceFolder -File -Filter '*.ps1' | ForEach-Object -Process {$_.FullName}) 53 | foreach ($DSCSourceFilePath in $DSCSourceFilePaths) { 54 | $DSCArchiveFilePath = $DSCSourceFilePath.Substring(0, $DSCSourceFilePath.Length - 4) + '.zip' 55 | Publish-AzureRmVMDscConfiguration $DSCSourceFilePath -OutputArchivePath $DSCArchiveFilePath -Force -Verbose 56 | } 57 | } 58 | 59 | # Create a storage account name if none was provided 60 | if ($StorageAccountName -eq '') { 61 | $StorageAccountName = 'stage' + ((Get-AzureRmContext).Subscription.SubscriptionId).Replace('-', '').substring(0, 19) 62 | } 63 | 64 | $StorageAccount = (Get-AzureRmStorageAccount | Where-Object{$_.StorageAccountName -eq $StorageAccountName}) 65 | 66 | # Create the storage account if it doesn't already exist 67 | if ($StorageAccount -eq $null) { 68 | $StorageResourceGroupName = 'ARM_Deploy_Staging' 69 | New-AzureRmResourceGroup -Location "$ResourceGroupLocation" -Name $StorageResourceGroupName -Force 70 | $StorageAccount = New-AzureRmStorageAccount -StorageAccountName $StorageAccountName -Type 'Standard_LRS' -ResourceGroupName $StorageResourceGroupName -Location "$ResourceGroupLocation" 71 | } 72 | 73 | # Generate the value for artifacts location if it is not provided in the parameter file 74 | if ($OptionalParameters[$ArtifactsLocationName] -eq $null) { 75 | $OptionalParameters[$ArtifactsLocationName] = $StorageAccount.Context.BlobEndPoint + $StorageContainerName 76 | } 77 | 78 | # Copy files from the local storage staging location to the storage account container 79 | New-AzureStorageContainer -Name $StorageContainerName -Context $StorageAccount.Context -ErrorAction SilentlyContinue *>&1 80 | 81 | $ArtifactFilePaths = Get-ChildItem $ArtifactStagingDirectory -Recurse -File | ForEach-Object -Process {$_.FullName} 82 | foreach ($SourcePath in $ArtifactFilePaths) { 83 | Set-AzureStorageBlobContent -File $SourcePath -Blob $SourcePath.Substring($ArtifactStagingDirectory.length + 1) ` 84 | -Container $StorageContainerName -Context $StorageAccount.Context -Force 85 | } 86 | 87 | # Generate a 4 hour SAS token for the artifacts location if one was not provided in the parameters file 88 | if ($OptionalParameters[$ArtifactsLocationSasTokenName] -eq $null) { 89 | $OptionalParameters[$ArtifactsLocationSasTokenName] = ConvertTo-SecureString -AsPlainText -Force ` 90 | (New-AzureStorageContainerSASToken -Container $StorageContainerName -Context $StorageAccount.Context -Permission r -ExpiryTime (Get-Date).AddHours(4)) 91 | } 92 | } 93 | 94 | # Create or update the resource group using the specified template file and template parameters file 95 | New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force 96 | 97 | if ($ValidateOnly) { 98 | $ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName ` 99 | -TemplateFile $TemplateFile ` 100 | -TemplateParameterFile $TemplateParametersFile ` 101 | @OptionalParameters) 102 | if ($ErrorMessages) { 103 | Write-Output '', 'Validation returned the following errors:', @($ErrorMessages), '', 'Template is invalid.' 104 | } 105 | else { 106 | Write-Output '', 'Template is valid.' 107 | } 108 | } 109 | else { 110 | New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` 111 | -ResourceGroupName $ResourceGroupName ` 112 | -TemplateFile $TemplateFile ` 113 | -TemplateParameterFile $TemplateParametersFile ` 114 | @OptionalParameters ` 115 | -Force -Verbose ` 116 | -ErrorVariable ErrorMessages 117 | if ($ErrorMessages) { 118 | Write-Output '', 'Template deployment returned the following errors:', @(@($ErrorMessages) | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") }) 119 | } 120 | } -------------------------------------------------------------------------------- /Deployment.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | bin\$(Configuration)\ 7 | false 8 | true 9 | false 10 | None 11 | obj\ 12 | $(BaseIntermediateOutputPath)\ 13 | $(BaseIntermediateOutputPath)$(Configuration)\ 14 | $(IntermediateOutputPath)ProjectReferences 15 | $(ProjectReferencesOutputPath)\ 16 | true 17 | 18 | 19 | 20 | false 21 | false 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Always 33 | 34 | 35 | Never 36 | 37 | 38 | false 39 | Build 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | _GetDeploymentProjectContent; 48 | _CalculateContentOutputRelativePaths; 49 | _GetReferencedProjectsOutput; 50 | _CalculateArtifactStagingDirectory; 51 | _CopyOutputToArtifactStagingDirectory; 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | Configuration=$(Configuration);Platform=$(Platform) 69 | 70 | 71 | 75 | 76 | 77 | 78 | $([System.IO.Path]::GetFileNameWithoutExtension('%(ProjectReference.Identity)')) 79 | 80 | 81 | 82 | 83 | 84 | 85 | $(OutDir) 86 | $(OutputPath) 87 | $(ArtifactStagingDirectory)\ 88 | $(ArtifactStagingDirectory)staging\ 89 | $(Build_StagingDirectory) 90 | 91 | 92 | 93 | 94 | 96 | 97 | <_OriginalIdentity>%(DeploymentProjectContentOutput.Identity) 98 | <_RelativePath>$(_OriginalIdentity.Replace('$(MSBuildProjectDirectory)', '')) 99 | 100 | 101 | 102 | 103 | $(_RelativePath) 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | PrepareForRun 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serverless-tutorial-deployment 2 | Part of the Channel 9 walkthrough on deploying serverless applications 3 | 4 | [Azure Function Code](https://github.com/jeffhollan/serverless-tutorial-functions) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /azuredeploy.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffhollan/serverless-tutorial-deployment/c33ee3d684e4d9def993e8e09f1562d80f59248e/azuredeploy.json -------------------------------------------------------------------------------- /azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "logicAppName": { 6 | "value": "csv-to-email" 7 | }, 8 | "ftpserverAddress": { 9 | "value": "myserver" 10 | }, 11 | "ftpuserName": { 12 | "value": "myFTP\\username" 13 | }, 14 | "ftppassword": { 15 | "value": "somepassword" 16 | }, 17 | "ftpserverPort": { 18 | "value": 21 19 | }, 20 | "ftpisssl": { 21 | "value": true 22 | }, 23 | "ftpisBinaryTransport": { 24 | "value": true 25 | }, 26 | "ftpdisableCertificateValidation": { 27 | "value": true 28 | }, 29 | "repoUrl": { 30 | "value": "https://github.com/jeffhollan/serverless-tutorial-functions.git" 31 | }, 32 | "appName": { 33 | "value": "uniqueFunctionName" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /resource-group-template.deployproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | Release 10 | AnyCPU 11 | 12 | 13 | 14 | e33f1d9e-63b1-4e89-a0dd-870d90bca52c 15 | 16 | 17 | Deployment 18 | 1.0 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | False 30 | 31 | 32 | 33 | 34 | 35 | 36 | --------------------------------------------------------------------------------