├── .gitattributes
├── .gitignore
├── BlobEventGridFunctionApp.sln
├── BlobEventGridFunctionApp
├── BlobEventGridFunctionApp.csproj
├── ProcessBlobEvents.cs
├── Properties
│ └── PublishProfiles
│ │ └── EventGridBlobEvents - Zip Deploy.pubxml
├── host.json
└── local.settings.json
├── Images
├── ApplicationInsights.png
├── Architecture.png
├── AzureEventGridViewer.png
├── AzureStorageExplorer.png
├── EventGrid.png
└── ServiceBusExplorer.png
├── README.md
└── Scripts
├── create-event-grid-subscription-for-azure-function.sh
├── create-event-grid-subscription-for-local-function.sh
└── create-event-grid-subscription-for-web-app.sh
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/BlobEventGridFunctionApp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.28307.168
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlobEventGridFunctionApp", "BlobEventGridFunctionApp\BlobEventGridFunctionApp.csproj", "{95A128BC-E819-4492-94F7-84BA4E5140CA}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{0D93B30B-6272-4A1C-9532-E35C5716EBED}"
9 | ProjectSection(SolutionItems) = preProject
10 | Scripts\create-event-grid-subscription-for-azure-function.sh = Scripts\create-event-grid-subscription-for-azure-function.sh
11 | Scripts\create-event-grid-subscription-for-local-function.sh = Scripts\create-event-grid-subscription-for-local-function.sh
12 | Scripts\create-event-grid-subscription-for-web-app.sh = Scripts\create-event-grid-subscription-for-web-app.sh
13 | EndProjectSection
14 | EndProject
15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{D4C18C87-7DAD-48A8-9A82-72B40983EF44}"
16 | ProjectSection(SolutionItems) = preProject
17 | Images\ApplicationInsights.png = Images\ApplicationInsights.png
18 | Images\Architecture.png = Images\Architecture.png
19 | Images\AzureEventGridViewer.png = Images\AzureEventGridViewer.png
20 | Images\AzureStorageExplorer.png = Images\AzureStorageExplorer.png
21 | Images\EventGrid.png = Images\EventGrid.png
22 | Images\ServiceBusExplorer.png = Images\ServiceBusExplorer.png
23 | EndProjectSection
24 | EndProject
25 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Readme", "Readme", "{12C31E30-96D4-4D37-82A7-840FB09E2ECD}"
26 | ProjectSection(SolutionItems) = preProject
27 | README.md = README.md
28 | EndProjectSection
29 | EndProject
30 | Global
31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
32 | Debug|Any CPU = Debug|Any CPU
33 | Release|Any CPU = Release|Any CPU
34 | EndGlobalSection
35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
36 | {95A128BC-E819-4492-94F7-84BA4E5140CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {95A128BC-E819-4492-94F7-84BA4E5140CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {95A128BC-E819-4492-94F7-84BA4E5140CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {95A128BC-E819-4492-94F7-84BA4E5140CA}.Release|Any CPU.Build.0 = Release|Any CPU
40 | EndGlobalSection
41 | GlobalSection(SolutionProperties) = preSolution
42 | HideSolutionNode = FALSE
43 | EndGlobalSection
44 | GlobalSection(ExtensibilityGlobals) = postSolution
45 | SolutionGuid = {74843D70-51B8-4564-9E99-8F20516ECEA1}
46 | EndGlobalSection
47 | EndGlobal
48 |
--------------------------------------------------------------------------------
/BlobEventGridFunctionApp/BlobEventGridFunctionApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp2.1
4 | v2
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | PreserveNewest
15 |
16 |
17 | PreserveNewest
18 | Never
19 |
20 |
21 |
--------------------------------------------------------------------------------
/BlobEventGridFunctionApp/ProcessBlobEvents.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paolosalvatori/blob-event-grid-function-app/83f4380c30b24685e9e567cad3edee89594741b7/BlobEventGridFunctionApp/ProcessBlobEvents.cs
--------------------------------------------------------------------------------
/BlobEventGridFunctionApp/Properties/PublishProfiles/EventGridBlobEvents - Zip Deploy.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | ZipDeploy
8 | AzureWebSite
9 | Release
10 | Any CPU
11 | http://eventgridblobevents.azurewebsites.net
12 | False
13 | /subscriptions/1a45a694-ae23-4650-9774-89a571c462f6/resourceGroups/EventGridBlobEventsResourceGroup/providers/Microsoft.Web/sites/EventGridBlobEvents
14 | $EventGridBlobEvents
15 | <_SavePWD>True
16 | https://eventgridblobevents.scm.azurewebsites.net/
17 |
18 |
--------------------------------------------------------------------------------
/BlobEventGridFunctionApp/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0"
3 | }
--------------------------------------------------------------------------------
/BlobEventGridFunctionApp/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet",
6 | "APPINSIGHTS_INSTRUMENTATIONKEY": "",
7 | "ServiceBusConnectionString": "",
8 | "QueueName": ""
9 | }
10 | }
--------------------------------------------------------------------------------
/Images/ApplicationInsights.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paolosalvatori/blob-event-grid-function-app/83f4380c30b24685e9e567cad3edee89594741b7/Images/ApplicationInsights.png
--------------------------------------------------------------------------------
/Images/Architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paolosalvatori/blob-event-grid-function-app/83f4380c30b24685e9e567cad3edee89594741b7/Images/Architecture.png
--------------------------------------------------------------------------------
/Images/AzureEventGridViewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paolosalvatori/blob-event-grid-function-app/83f4380c30b24685e9e567cad3edee89594741b7/Images/AzureEventGridViewer.png
--------------------------------------------------------------------------------
/Images/AzureStorageExplorer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paolosalvatori/blob-event-grid-function-app/83f4380c30b24685e9e567cad3edee89594741b7/Images/AzureStorageExplorer.png
--------------------------------------------------------------------------------
/Images/EventGrid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paolosalvatori/blob-event-grid-function-app/83f4380c30b24685e9e567cad3edee89594741b7/Images/EventGrid.png
--------------------------------------------------------------------------------
/Images/ServiceBusExplorer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paolosalvatori/blob-event-grid-function-app/83f4380c30b24685e9e567cad3edee89594741b7/Images/ServiceBusExplorer.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | services: azure-functions, app-service, azure-monitor, event-grid, service-bus-messaging, storage
3 | platforms: dotnet-core, aspnet-core
4 | author: paolosalvatori
5 | ---
6 |
7 | ## Consume Blob Event Grid events with an Azure Function
8 | This sample demonstrates how to create a serverless application to receive and process events via an Azure Event Grid Subscription any time a blob is created, updated or deleted in a given container inside an [Azure storage account](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-overview).
9 |
10 | ## What is Azure Event Grid?
11 | [Azure Event Grid](https://docs.microsoft.com/en-us/azure/event-grid/overview) is a fully-managed intelligent event routing service that allows for uniform event consumption using a publish-subscribe model. Azure Event Grid allows you to easily build event-processging applications using a microservice and serverless architecture. The Azure Event Grid support a rich set of publishers and consumers. A publisher is the service or resource that originates the event. For example, an Azure blob storage account is a publisher, and a blob upload or deletion is an event. Some Azure services have built-in support for publishing events to Event Grid.
12 |
13 | 
14 |
15 | ## Publishers or Event Sources
16 | For full details on the capabilities of each event publisher as well as related articles, see [event sources](https://docs.microsoft.com/en-us/azure/event-grid/event-sources). Currently, the following Azure services support sending events to Event Grid:
17 |
18 | - Azure Subscriptions (management operations)
19 | - Container Registry
20 | - Custom Topics
21 | - Event Hubs
22 | - IoT Hub
23 | - Media Services
24 | - Resource Groups (management operations)
25 | - Service Bus
26 | - Storage Blob
27 | - Storage General-purpose v2 (GPv2)
28 |
29 | ## Event Handlers or Consumers
30 | For full details on the capabilities of each event consumer as well as related articles, see [event handlers](https://docs.microsoft.com/en-us/azure/event-grid/event-handlers). Currently, the following Azure services support handling events from Event Grid:
31 |
32 | - Azure Automation
33 | - Azure Functions
34 | - Event Hubs
35 | - Hybrid Connections
36 | - Logic Apps
37 | - Microsoft Flow
38 | - Queue Storage
39 | - WebHooks
40 |
41 | ## Architecture
42 | The following picture shows the architecture design of the application.
43 |
44 | 
45 |
46 | ## Message Flow
47 | 1. Use the [Azure Storage Explorer](https://azure.microsoft.com/en-us/features/storage-explorer/) to upload one more files as blobs to a container in an Azure storage account.
48 | 2. The Azure Storage Account Topic sends an event to 3 Azure Event Grid Subscriptions:
49 | - Azure Function Subscription: this subscription is configured to send events to the webhook endpoint exposed by an Azure Function that makes use of [Event Grid trigger for Azure Functions](https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-event-grid)
50 | - Local Debugging Subscription: this subscription is configured to send events to a local function on your via a public HTTP/S endpoint exposed by [ngrok](https://ngrok.com/) on your development machine. For more information, see [Azure Function Event Grid Trigger Local Debugging](https://docs.microsoft.com/en-us/azure/azure-functions/functions-debug-event-grid-trigger-local).
51 | - Azure Event Grid Viewer: this subscription is configured to send events to a pre-built Azure Web App that displays the event messages. For more information on how to use this web app to see the events generated by an Azure Event Grid Topic, see [Azure Event Grid Viewer](https://github.com/Azure-Samples/azure-event-grid-viewer). For more information on how to send storage events to a web endpoint with Azure CLI, see the [Quickstart: Route storage events to web endpoint with Azure CLI](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-event-quickstart).
52 | 3. An Azure Function uses the [Event Grid trigger for Azure Functions](https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-event-grid) to receive events via an Event Grid Subscription and send events to a queue in a Service Bus namespace. You can use the [Service Bus Explorer](https://github.com/paolosalvatori/ServiceBusExplorer) to receive or peek messages from the queue. The Azure Function is instrumented to collect and send metrics and logs to Application Insights.
53 |
54 | ## Blob Trigger vs Event Grid Trigger
55 | The [Blob storage trigger](https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-blob#trigger) starts a function when a new or updated blob is detected. The blob contents are provided as input to the function. The [Event Grid](https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-event-grid) trigger has built-in support for blob events and can also be used to start a function when a new or updated blob is detected. When using the Blob storage trigger, you can directly access the content of the new or updated blob.
56 | Use Event Grid instead of the Blob storage trigger for the following scenarios:
57 | * Blob storage accounts: Blob storage accounts are supported for blob input and output bindings
58 | but not for blob triggers. Blob storage triggers require a general-purpose storage account.
59 | * High scale: high scale can be loosely defined as containers that have more than 100,000 blobs
60 | in them or storage accounts that have more than 100 blob updates per second.
61 | * Minimizing latency: if your function app is on the Consumption plan, there can be up to a 10-minute delay
62 | in processing new blobs if a function app has gone idle. To avoid this latency, you can switch to an App
63 | Service plan with Always On enabled. You can also use an Event Grid trigger with your Blob storage account.
64 | * Blob delete events: you cannot handle blob delete events with the Blob Storage trigger
65 |
66 |
67 | ## Configuration
68 | Make sure to specify the following data in the local.settings.json:
69 |
70 | - [AzureWebJobStorage](https://docs.microsoft.com/en-us/azure/azure-functions/functions-app-settings#azurewebjobsstorage): the Azure Functions runtime uses this storage account connection string for all functions except for HTTP triggered functions.
71 | - [APPINSIGHTS_INSTRUMENTATIONKEY](https://docs.microsoft.com/en-us/azure/azure-functions/functions-app-settings#appinsightsinstrumentationkey): The Application Insights instrumentation key if you're using Application Insights. For more information, see [Monitor Azure Functions](https://docs.microsoft.com/en-us/azure/azure-functions/functions-monitoring).
72 | - ServiceBusConnectionString: the connection string of the Service Bus namespace containing the queue where the Azure Function sends messages to.
73 | - QueueName: the names of the queue.
74 |
75 | ```elixir
76 | {
77 | "IsEncrypted": false,
78 | "Values": {
79 | "AzureWebJobsStorage": "",
80 | "FUNCTIONS_WORKER_RUNTIME": "dotnet",
81 | "APPINSIGHTS_INSTRUMENTATIONKEY": "",
82 | "ServiceBusConnectionString": "",
83 | "QueueName": ""
84 | }
85 | }
86 | ```
87 | ## Azure Function Code
88 | The following table contains the code of the Azure Function.
89 |
90 | ```csharp
91 | #region Using Directives
92 | using System;
93 | using System.Threading.Tasks;
94 | using Microsoft.Azure.EventGrid.Models;
95 | using Microsoft.Azure.WebJobs;
96 | using Microsoft.Azure.WebJobs.ServiceBus;
97 | using Microsoft.ApplicationInsights;
98 | using Microsoft.ApplicationInsights.Extensibility;
99 | using Microsoft.Extensions.Logging;
100 | using Microsoft.Azure.ServiceBus;
101 | using System.Text;
102 | using Microsoft.Azure.WebJobs.Extensions.EventGrid;
103 | using Newtonsoft.Json.Linq;
104 | using System.Collections.Generic;
105 | #endregion
106 |
107 | namespace BlobEventGridFunctionApp
108 | {
109 | public static class ProcessBlobEvents
110 | {
111 | #region Private Constants
112 | private const string BlobCreatedEvent = "Microsoft.Storage.BlobCreated";
113 | private const string BlobDeletedEvent = "Microsoft.Storage.BlobDeleted";
114 | #endregion
115 |
116 | #region Private Static Fields
117 | private static readonly string key = TelemetryConfiguration.Active.InstrumentationKey = Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY", EnvironmentVariableTarget.Process);
118 | private static readonly TelemetryClient telemetry = new TelemetryClient() { InstrumentationKey = key };
119 | #endregion
120 |
121 | #region Azure Functions
122 | [FunctionName("ProcessBlobEvents")]
123 | public static async Task Run([EventGridTrigger]EventGridEvent eventGridEvent,
124 | [ServiceBus("%QueueName%", Connection = "ServiceBusConnectionString", EntityType = EntityType.Queue)] IAsyncCollector asyncCollector,
125 | ExecutionContext context,
126 | ILogger log)
127 | {
128 | try
129 | {
130 | if (eventGridEvent == null && string.IsNullOrWhiteSpace(eventGridEvent.EventType))
131 | {
132 | throw new ArgumentNullException("Null or Invalid Event Grid Event");
133 | }
134 |
135 | log.LogInformation($@"New Event Grid Event:
136 | - Id=[{eventGridEvent.Id}]
137 | - EventType=[{eventGridEvent.EventType}]
138 | - EventTime=[{eventGridEvent.EventTime}]
139 | - Subject=[{eventGridEvent.Subject}]
140 | - Topic=[{eventGridEvent.Topic}]");
141 |
142 | if (eventGridEvent.Data is JObject jObject)
143 | {
144 | // Create message
145 | var message = new Message(Encoding.UTF8.GetBytes(jObject.ToString()))
146 | {
147 | MessageId = eventGridEvent.Id
148 | };
149 |
150 | switch (eventGridEvent.EventType)
151 | {
152 | case BlobCreatedEvent:
153 | {
154 | var blobCreatedEvent = jObject.ToObject();
155 | var storageDiagnostics = JObject.Parse(blobCreatedEvent.StorageDiagnostics.ToString()).ToString(Newtonsoft.Json.Formatting.None);
156 |
157 | log.LogInformation($@"Received {BlobCreatedEvent} Event:
158 | - Api=[{blobCreatedEvent.Api}]
159 | - BlobType=[{blobCreatedEvent.BlobType}]
160 | - ClientRequestId=[{blobCreatedEvent.ClientRequestId}]
161 | - ContentLength=[{blobCreatedEvent.ContentLength}]
162 | - ContentType=[{blobCreatedEvent.ContentType}]
163 | - ETag=[{blobCreatedEvent.ETag}]
164 | - RequestId=[{blobCreatedEvent.RequestId}]
165 | - Sequencer=[{blobCreatedEvent.Sequencer}]
166 | - StorageDiagnostics=[{storageDiagnostics}]
167 | - Url=[{blobCreatedEvent.Url}]
168 | ");
169 |
170 | // Set message label
171 | message.Label = "BlobCreatedEvent";
172 |
173 | // Add custom properties
174 | message.UserProperties.Add("id", eventGridEvent.Id);
175 | message.UserProperties.Add("topic", eventGridEvent.Topic);
176 | message.UserProperties.Add("eventType", eventGridEvent.EventType);
177 | message.UserProperties.Add("eventTime", eventGridEvent.EventTime);
178 | message.UserProperties.Add("subject", eventGridEvent.Subject);
179 | message.UserProperties.Add("api", blobCreatedEvent.Api);
180 | message.UserProperties.Add("blobType", blobCreatedEvent.BlobType);
181 | message.UserProperties.Add("clientRequestId", blobCreatedEvent.ClientRequestId);
182 | message.UserProperties.Add("contentLength", blobCreatedEvent.ContentLength);
183 | message.UserProperties.Add("contentType", blobCreatedEvent.ContentType);
184 | message.UserProperties.Add("eTag", blobCreatedEvent.ETag);
185 | message.UserProperties.Add("requestId", blobCreatedEvent.RequestId);
186 | message.UserProperties.Add("sequencer", blobCreatedEvent.Sequencer);
187 | message.UserProperties.Add("storageDiagnostics", storageDiagnostics);
188 | message.UserProperties.Add("url", blobCreatedEvent.Url);
189 |
190 | // Add message to AsyncCollector
191 | await asyncCollector.AddAsync(message);
192 |
193 | // Telemetry
194 | telemetry.Context.Operation.Id = context.InvocationId.ToString();
195 | telemetry.Context.Operation.Name = "BlobCreatedEvent";
196 | telemetry.TrackEvent($"[{blobCreatedEvent.Url}] blob created");
197 | var properties = new Dictionary
198 | {
199 | { "BlobType", blobCreatedEvent.BlobType },
200 | { "ContentType ", blobCreatedEvent.ContentType }
201 | };
202 | telemetry.TrackMetric("ProcessBlobEvents Created", 1, properties);
203 | }
204 | break;
205 |
206 | case BlobDeletedEvent:
207 | {
208 | var blobDeletedEvent = jObject.ToObject();
209 | var storageDiagnostics = JObject.Parse(blobDeletedEvent.StorageDiagnostics.ToString()).ToString(Newtonsoft.Json.Formatting.None);
210 |
211 | log.LogInformation($@"Received {BlobDeletedEvent} Event:
212 | - Api=[{blobDeletedEvent.Api}]
213 | - BlobType=[{blobDeletedEvent.BlobType}]
214 | - ClientRequestId=[{blobDeletedEvent.ClientRequestId}]
215 | - ContentType=[{blobDeletedEvent.ContentType}]
216 | - RequestId=[{blobDeletedEvent.RequestId}]
217 | - Sequencer=[{blobDeletedEvent.Sequencer}]
218 | - StorageDiagnostics=[{storageDiagnostics}]
219 | - Url=[{blobDeletedEvent.Url}]
220 | ");
221 |
222 | // Set message label
223 | message.Label = "BlobDeletedEvent";
224 |
225 | // Add custom properties
226 | message.UserProperties.Add("id", eventGridEvent.Id);
227 | message.UserProperties.Add("topic", eventGridEvent.Topic);
228 | message.UserProperties.Add("eventType", eventGridEvent.EventType);
229 | message.UserProperties.Add("eventTime", eventGridEvent.EventTime);
230 | message.UserProperties.Add("subject", eventGridEvent.Subject);
231 | message.UserProperties.Add("api", blobDeletedEvent.Api);
232 | message.UserProperties.Add("blobType", blobDeletedEvent.BlobType);
233 | message.UserProperties.Add("clientRequestId", blobDeletedEvent.ClientRequestId);
234 | message.UserProperties.Add("contentType", blobDeletedEvent.ContentType);
235 | message.UserProperties.Add("requestId", blobDeletedEvent.RequestId);
236 | message.UserProperties.Add("sequencer", blobDeletedEvent.Sequencer);
237 | message.UserProperties.Add("storageDiagnostics", storageDiagnostics);
238 | message.UserProperties.Add("url", blobDeletedEvent.Url);
239 |
240 | // Add message to AsyncCollector
241 | await asyncCollector.AddAsync(message);
242 |
243 | // Telemetry
244 | telemetry.Context.Operation.Id = context.InvocationId.ToString();
245 | telemetry.Context.Operation.Name = "BlobDeletedEvent";
246 | telemetry.TrackEvent($"[{blobDeletedEvent.Url}] blob deleted");
247 | var properties = new Dictionary
248 | {
249 | { "BlobType", blobDeletedEvent.BlobType },
250 | { "ContentType ", blobDeletedEvent.ContentType }
251 | };
252 | telemetry.TrackMetric("ProcessBlobEvents Deleted", 1, properties);
253 | }
254 | break;
255 | }
256 | }
257 | }
258 | catch (Exception ex)
259 | {
260 | log.LogError(ex, ex.Message);
261 | throw;
262 | }
263 | }
264 | #endregion
265 | }
266 | }
267 | ```
268 |
269 | ## Scripts
270 | The solution includes 3 bash scripts can be used to create the 3 Azure Event Grid Subscriptions used to send events any time a blob is created, updated or deleted in the files container in the source storage account, respectively to:
271 |
272 | - Azure Function running on Azure
273 | - Azure Funtion running in you development machine
274 | - Event Grid Event Viewer web app on Azure
275 |
276 | Make sure to properly assign a value to variables before running the script. Also make sure to run scripts using an Azure AD account that is the owner or a contributor of the Azure subscription where you intend to deploy the solution.
277 |
278 | **create-event-grid-subscription-for-azure-function.sh**
279 | ```bash
280 | #!/bin/bash
281 |
282 | # variables
283 | location="WestEurope"
284 | storageAccountName="babofiles"
285 | storageAccountResourceGroup="BaboFilesResourceGroup"
286 | functionAppName="EventGridBlobEvents"
287 | functionAppResourceGroup="EventGridBlobEventsResourceGroup"
288 | functionName="ProcessBlobEvents"
289 | subscriptionName='BaboFilesAzureFunctionSubscriber'
290 | deadLetterContainerName="deadletter"
291 | filesContainerName="files"
292 | subjectBeginsWith="/blobServices/default/containers/"$filesContainerName
293 |
294 | # functions
295 | function getEventGridExtensionKey
296 | {
297 | # get Kudu username
298 | echo "Retrieving username from ["$functionAppName"] Azure Function publishing profile..."
299 | username=$(az functionapp deployment list-publishing-profiles --name $1 --resource-group $2 --query '[?publishMethod==`MSDeploy`].userName' --output tsv)
300 |
301 | if [ -n $username ]; then
302 | echo "["$username"] username successfully retrieved"
303 | else
304 | echo "No username could be retrieved"
305 | return
306 | fi
307 |
308 | # get Kudu password
309 | echo "Retrieving password from ["$functionAppName"] Azure Function publishing profile..."
310 | password=$(az functionapp deployment list-publishing-profiles --name $1 --resource-group $2 --query '[?publishMethod==`MSDeploy`].userPWD' --output tsv)
311 |
312 | if [ -n $password ]; then
313 | echo "["$password"] password successfully retrieved"
314 | else
315 | echo "No password could be retrieved"
316 | return
317 | fi
318 |
319 | # get jwt
320 | echo "Retrieving JWT token from Azure Function \ Kudu Management API..."
321 | jwt=$(sed -e 's/^"//' -e 's/"$//' <<< $(curl https://$functionAppName.scm.azurewebsites.net/api/functions/admin/token --user $username":"$password --silent))
322 |
323 | if [ -n $jwt ]; then
324 | echo "JWT token successfully retrieved"
325 | else
326 | echo "No JWT token could be retrieved"
327 | return
328 | fi
329 |
330 | # get eventgrid_extension key
331 | echo "Retrieving [eventgrid_extension] key..."
332 | eventGridExtensionKey=$(sed -e 's/^"//' -e 's/"$//' <<< $(curl -H 'Accept: application/json' -H "Authorization: Bearer ${jwt}" https://$functionAppName.azurewebsites.net/admin/host/systemkeys/eventgrid_extension --silent | jq .value))
333 |
334 | if [ -n $eventGridExtensionKey ]; then
335 | echo "[eventgrid_extension] key successfully retrieved"
336 | else
337 | echo "No [eventgrid_extension] key could be retrieved"
338 | return
339 | fi
340 | }
341 |
342 | # check if the storage account exists
343 | echo "Checking if ["$storageAccountName"] storage account actually exists..."
344 |
345 | set +e
346 | (
347 | az storage account show --name $storageAccountName --resource-group $storageAccountResourceGroup &> /dev/null
348 | )
349 |
350 | if [ $? != 0 ]; then
351 | echo "No ["$storageAccountName"] storage account actually exists"
352 | set -e
353 | (
354 | # create the storage account
355 | az storage account create \
356 | --name $storageAccountName \
357 | --resource-group $storageAccountResourceGroup \
358 | --location $location \
359 | --sku Standard_LRS \
360 | --kind BlobStorage \
361 | --access-tier Hot 1> /dev/null
362 | )
363 | echo "["$storageAccountName"] storage account successfully created"
364 | else
365 | echo "["$storageAccountName"] storage account already exists"
366 | fi
367 |
368 | # get storage account connection string
369 | echo "Retrieving the connection string for ["$storageAccountName"] storage account..."
370 | connectionString=$(az storage account show-connection-string --name $storageAccountName --resource-group $storageAccountResourceGroup --query connectionString --output tsv)
371 |
372 | if [ -n $connectionString ]; then
373 | echo "The connection string for ["$storageAccountName"] storage account is ["$connectionString"]"
374 | else
375 | echo "Failed to retrieve the connection string for ["$storageAccountName"] storage account"
376 | return
377 | fi
378 |
379 | # checking if deadletter container exists
380 | echo "Checking if ["$deadLetterContainerName"] container already exists..."
381 | set +e
382 | (
383 | az storage container show --name $deadLetterContainerName --connection-string $connectionString &> /dev/null
384 | )
385 |
386 | if [ $? != 0 ]; then
387 | echo "No ["$deadLetterContainerName"] container actually exists in ["$storageAccountName"] storage account"
388 | set -e
389 | (
390 | # create deadletter container
391 | az storage container create \
392 | --name $deadLetterContainerName \
393 | --public-access off \
394 | --connection-string $connectionString 1> /dev/null
395 | )
396 | echo "["$deadLetterContainerName"] container successfully created in ["$storageAccountName"] storage account"
397 | else
398 | echo "A container called ["$deadLetterContainerName"] already exists in ["$storageAccountName"] storage account"
399 | fi
400 |
401 | # checking if files container exists
402 | echo "Checking if ["$filesContainerName"] container already exists..."
403 | set +e
404 | (
405 | az storage container show --name $filesContainerName --connection-string $connectionString &> /dev/null
406 | )
407 |
408 | if [ $? != 0 ]; then
409 | echo "No ["$filesContainerName"] container actually exists in ["$storageAccountName"] storage account"
410 | set -e
411 | (
412 | # create files container
413 | az storage container create \
414 | --name $filesContainerName \
415 | --public-access off \
416 | --connection-string $connectionString 1> /dev/null
417 | )
418 | echo "["$filesContainerName"] container successfully created in ["$storageAccountName"] storage account"
419 | else
420 | echo "A container called ["$filesContainerName"] already exists in ["$storageAccountName"] storage account"
421 | fi
422 |
423 | # retrieve resource id for the storage account
424 | echo "Retrieving the resource id for ["$storageAccountName"] storage account..."
425 | storageAccountId=$(az storage account show --name $storageAccountName --resource-group $storageAccountResourceGroup --query id --output tsv 2> /dev/null)
426 |
427 | if [ -n $storageAccountId ]; then
428 | echo "Resource id for ["$storageAccountName"] storage account successfully retrieved: ["$storageAccountId"]"
429 | else
430 | echo "Failed to retrieve resource id for ["$storageAccountName"] storage account"
431 | return
432 | fi
433 |
434 | # retrieve eventgrid_extensionkey
435 | getEventGridExtensionKey $functionAppName $functionAppResourceGroup
436 |
437 | if [ -z $eventGridExtensionKey ]; then
438 | echo "Failed to retrieve eventgrid_extensionkey"
439 | return
440 | fi
441 |
442 | # creating the endpoint URL for the Azure Function
443 | endpointUrl="https://$functionAppName.azurewebsites.net/runtime/webhooks/eventgrid?functionName=$functionName&code=$eventGridExtensionKey"
444 |
445 | echo "The endpoint for the ["$functionName"] function in the ["$functionAppName"] function app is ["$endpointUrl"]"
446 |
447 | echo "Checking if Azure CLI eventgrid extension is installed..."
448 | set +e
449 | (
450 | az extension show --name eventgrid --query name --output tsv &> /dev/null
451 | )
452 |
453 | if [ $? != 0 ]; then
454 | echo "The Azure CLI eventgrid extension was not found. Installing the extension..."
455 | az extension add --name eventgrid
456 | else
457 | echo "Azure CLI eventgrid extension successfully found. Updating the extension to the latest version..."
458 | az extension update --name eventgrid
459 | fi
460 |
461 | # checking if the subscription already exists
462 | echo "Checking if ["$subscriptionName"] Event Grid subscription already exists for ["$storageAccountName"] storage account..."
463 | set +e
464 | (
465 | az eventgrid event-subscription show --name $subscriptionName --source-resource-id $storageAccountId &> /dev/null
466 | )
467 |
468 | if [ $? != 0 ]; then
469 | echo "No ["$subscriptionName"] Event Grid subscription actually exists for ["$storageAccountName"] storage account"
470 | echo "Creating a subscription for the ["$endpointUrl"] endpoint of the ["$functionName"] Azure Function..."
471 |
472 | set +e
473 | (
474 | az eventgrid event-subscription create \
475 | --source-resource-id $storageAccountId \
476 | --name $subscriptionName \
477 | --endpoint-type webhook \
478 | --endpoint $endpointUrl \
479 | --subject-begins-with $subjectBeginsWith \
480 | --deadletter-endpoint $storageAccountId/blobServices/default/containers/$deadLetterContainerName 1> /dev/null
481 | )
482 |
483 | if [ $? == 0 ]; then
484 | echo "["$subscriptionName"] Event Grid subscription successfully created"
485 | fi
486 | else
487 | echo "An Event Grid subscription called ["$subscriptionName"] already exists for ["$storageAccountName"] storage account"
488 | fi
489 | ```
490 |
491 | **create-event-grid-subscription-for-local-function.sh**
492 | ```bash
493 | #!/bin/bash
494 |
495 | # variables
496 | location="WestEurope"
497 | storageAccountName="babofiles"
498 | storageAccountResourceGroup="BaboFilesResourceGroup"
499 | subscriptionName='BaboFilesLocalDebugging'
500 | ngrockSubdomain="db1abac5"
501 | functionName="ProcessBlobEvents"
502 | endpointUrl="https://"$ngrockSubdomain".ngrok.io/runtime/webhooks/EventGrid?functionName="$functionName
503 | deadLetterContainerName="deadletter"
504 | filesContainerName="files"
505 | subjectBeginsWith="/blobServices/default/containers/"$filesContainerName
506 |
507 | # check if the storage account exists
508 | echo "Checking if ["$storageAccountName"] storage account actually exists..."
509 |
510 | set +e
511 | (
512 | az storage account show --name $storageAccountName --resource-group $storageAccountResourceGroup &> /dev/null
513 | )
514 |
515 | if [ $? != 0 ]; then
516 | echo "No ["$storageAccountName"] storage account actually exists"
517 | set -e
518 | (
519 | # create the storage account
520 | az storage account create \
521 | --name $storageAccountName \
522 | --resource-group $storageAccountResourceGroup \
523 | --location $location \
524 | --sku Standard_LRS \
525 | --kind BlobStorage \
526 | --access-tier Hot 1> /dev/null
527 | )
528 | echo "["$storageAccountName"] storage account successfully created"
529 | else
530 | echo "["$storageAccountName"] storage account already exists"
531 | fi
532 |
533 | # get storage account connection string
534 | echo "Retrieving the connection string for ["$storageAccountName"] storage account..."
535 | connectionString=$(az storage account show-connection-string --name $storageAccountName --resource-group $storageAccountResourceGroup --query connectionString --output tsv)
536 |
537 | if [ -n $connectionString ]; then
538 | echo "The connection string for ["$storageAccountName"] storage account is ["$connectionString"]"
539 | else
540 | echo "Failed to retrieve the connection string for ["$storageAccountName"] storage account"
541 | return
542 | fi
543 |
544 | # checking if deadletter container exists
545 | echo "Checking if ["$deadLetterContainerName"] container already exists..."
546 | set +e
547 | (
548 | az storage container show --name $deadLetterContainerName --connection-string $connectionString &> /dev/null
549 | )
550 |
551 | if [ $? != 0 ]; then
552 | echo "No ["$deadLetterContainerName"] container actually exists in ["$storageAccountName"] storage account"
553 | set -e
554 | (
555 | # create deadletter container
556 | az storage container create \
557 | --name $deadLetterContainerName \
558 | --public-access off \
559 | --connection-string $connectionString 1> /dev/null
560 | )
561 | echo "["$deadLetterContainerName"] container successfully created in ["$storageAccountName"] storage account"
562 | else
563 | echo "A container called ["$deadLetterContainerName"] already exists in ["$storageAccountName"] storage account"
564 | fi
565 |
566 | # checking if files container exists
567 | echo "Checking if ["$filesContainerName"] container already exists..."
568 | set +e
569 | (
570 | az storage container show --name $filesContainerName --connection-string $connectionString &> /dev/null
571 | )
572 |
573 | if [ $? != 0 ]; then
574 | echo "No ["$filesContainerName"] container actually exists in ["$storageAccountName"] storage account"
575 | set -e
576 | (
577 | # create files container
578 | az storage container create \
579 | --name $filesContainerName \
580 | --public-access off \
581 | --connection-string $connectionString 1> /dev/null
582 | )
583 | echo "["$filesContainerName"] container successfully created in ["$storageAccountName"] storage account"
584 | else
585 | echo "A container called ["$filesContainerName"] already exists in ["$storageAccountName"] storage account"
586 | fi
587 |
588 | # retrieve resource id for the storage account
589 | echo "Retrieving the resource id for ["$storageAccountName"] storage account..."
590 | storageAccountId=$(az storage account show --name $storageAccountName --resource-group $storageAccountResourceGroup --query id --output tsv 2> /dev/null)
591 |
592 | if [ -n $storageAccountId ]; then
593 | echo "Resource id for ["$storageAccountName"] storage account successfully retrieved: ["$storageAccountId"]"
594 | else
595 | echo "Failed to retrieve resource id for ["$storageAccountName"] storage account"
596 | return
597 | fi
598 |
599 | echo "Checking if Azure CLI eventgrid extension is installed..."
600 | set +e
601 | (
602 | az extension show --name eventgrid --query name --output tsv &> /dev/null
603 | )
604 |
605 | if [ $? != 0 ]; then
606 | echo "The Azure CLI eventgrid extension was not found. Installing the extension..."
607 | az extension add --name eventgrid
608 | else
609 | echo "Azure CLI eventgrid extension successfully found. Updating the extension to the latest version..."
610 | az extension update --name eventgrid
611 | fi
612 |
613 | # checking if the subscription already exists
614 | echo "Checking if ["$subscriptionName"] Event Grid subscription already exists for ["$storageAccountName"] storage account..."
615 | set +e
616 | (
617 | az eventgrid event-subscription show --name $subscriptionName --source-resource-id $storageAccountId &> /dev/null
618 | )
619 |
620 | if [ $? != 0 ]; then
621 | echo "No ["$subscriptionName"] Event Grid subscription actually exists for ["$storageAccountName"] storage account"
622 | echo "Creating a subscription for the ["$endpointUrl"] ngrock local endpoint..."
623 |
624 | set +e
625 | (
626 | az eventgrid event-subscription create \
627 | --source-resource-id $storageAccountId \
628 | --name $subscriptionName \
629 | --endpoint-type webhook \
630 | --endpoint $endpointUrl \
631 | --subject-begins-with $subjectBeginsWith \
632 | --deadletter-endpoint $storageAccountId/blobServices/default/containers/$deadLetterContainerName 1> /dev/null
633 | )
634 |
635 | if [ $? == 0 ]; then
636 | echo "["$subscriptionName"] Event Grid subscription successfully created"
637 | fi
638 | else
639 | echo "An Event Grid subscription called ["$subscriptionName"] already exists for ["$storageAccountName"] storage account"
640 | fi
641 | ```
642 |
643 | **create-event-grid-subscription-for-web-app.sh**
644 | ```bash
645 | #!/bin/bash
646 |
647 | # variables
648 | location="WestEurope"
649 | storageAccountName="babofiles"
650 | storageAccountResourceGroup="BaboFilesResourceGroup"
651 | subscriptionName='BaboFilesWebApp'
652 | webAppSubdomain="baboeventgridviewer"
653 | functionName="ProcessBlobEvents"
654 | endpointUrl="https://"$webAppSubdomain".azurewebsites.net/api/updates"
655 | deadLetterContainerName="deadletter"
656 | filesContainerName="files"
657 |
658 | # check if the storage account exists
659 | echo "Checking if ["$storageAccountName"] storage account actually exists..."
660 |
661 | set +e
662 | (
663 | az storage account show --name $storageAccountName --resource-group $storageAccountResourceGroup &> /dev/null
664 | )
665 |
666 | if [ $? != 0 ]; then
667 | echo "No ["$storageAccountName"] storage account actually exists"
668 | set -e
669 | (
670 | # create the storage account
671 | az storage account create \
672 | --name $storageAccountName \
673 | --resource-group $storageAccountResourceGroup \
674 | --location $location \
675 | --sku Standard_LRS \
676 | --kind BlobStorage \
677 | --access-tier Hot 1> /dev/null
678 | )
679 | echo "["$storageAccountName"] storage account successfully created"
680 | else
681 | echo "["$storageAccountName"] storage account already exists"
682 | fi
683 |
684 | # get storage account connection string
685 | echo "Retrieving the connection string for ["$storageAccountName"] storage account..."
686 | connectionString=$(az storage account show-connection-string --name $storageAccountName --resource-group $storageAccountResourceGroup --query connectionString --output tsv)
687 |
688 | if [ -n $connectionString ]; then
689 | echo "The connection string for ["$storageAccountName"] storage account is ["$connectionString"]"
690 | else
691 | echo "Failed to retrieve the connection string for ["$storageAccountName"] storage account"
692 | return
693 | fi
694 |
695 | # checking if deadletter container exists
696 | echo "Checking if ["$deadLetterContainerName"] container already exists..."
697 | set +e
698 | (
699 | az storage container show --name $deadLetterContainerName --connection-string $connectionString &> /dev/null
700 | )
701 |
702 | if [ $? != 0 ]; then
703 | echo "No ["$deadLetterContainerName"] container actually exists in ["$storageAccountName"] storage account"
704 | set -e
705 | (
706 | # create deadletter container
707 | az storage container create \
708 | --name $deadLetterContainerName \
709 | --public-access off \
710 | --connection-string $connectionString 1> /dev/null
711 | )
712 | echo "["$deadLetterContainerName"] container successfully created in ["$storageAccountName"] storage account"
713 | else
714 | echo "A container called ["$deadLetterContainerName"] already exists in ["$storageAccountName"] storage account"
715 | fi
716 |
717 | # checking if files container exists
718 | echo "Checking if ["$filesContainerName"] container already exists..."
719 | set +e
720 | (
721 | az storage container show --name $filesContainerName --connection-string $connectionString &> /dev/null
722 | )
723 |
724 | if [ $? != 0 ]; then
725 | echo "No ["$filesContainerName"] container actually exists in ["$storageAccountName"] storage account"
726 | set -e
727 | (
728 | # create files container
729 | az storage container create \
730 | --name $filesContainerName \
731 | --public-access off \
732 | --connection-string $connectionString 1> /dev/null
733 | )
734 | echo "["$filesContainerName"] container successfully created in ["$storageAccountName"] storage account"
735 | else
736 | echo "A container called ["$filesContainerName"] already exists in ["$storageAccountName"] storage account"
737 | fi
738 |
739 | # retrieve resource id for the storage account
740 | echo "Retrieving the resource id for ["$storageAccountName"] storage account..."
741 | storageAccountId=$(az storage account show --name $storageAccountName --resource-group $storageAccountResourceGroup --query id --output tsv 2> /dev/null)
742 |
743 | if [ -n $storageAccountId ]; then
744 | echo "Resource id for ["$storageAccountName"] storage account successfully retrieved: ["$storageAccountId"]"
745 | else
746 | echo "Failed to retrieve resource id for ["$storageAccountName"] storage account"
747 | return
748 | fi
749 |
750 | echo "Checking if Azure CLI eventgrid extension is installed..."
751 | set +e
752 | (
753 | az extension show --name eventgrid --query name --output tsv &> /dev/null
754 | )
755 |
756 | if [ $? != 0 ]; then
757 | echo "The Azure CLI eventgrid extension was not found. Installing the extension..."
758 | az extension add --name eventgrid
759 | else
760 | echo "Azure CLI eventgrid extension successfully found. Updating the extension to the latest version..."
761 | az extension update --name eventgrid
762 | fi
763 |
764 | # checking if the subscription already exists
765 | echo "Checking if ["$subscriptionName"] Event Grid subscription already exists for ["$storageAccountName"] storage account..."
766 | set +e
767 | (
768 | az eventgrid event-subscription show --name $subscriptionName --source-resource-id $storageAccountId &> /dev/null
769 | )
770 |
771 | if [ $? != 0 ]; then
772 | echo "No ["$subscriptionName"] Event Grid subscription actually exists for ["$storageAccountName"] storage account"
773 | echo "Creating the subscription for the ["$endpointUrl"] endpoint of the Azure Event Grid Viewer web app..."
774 | set +e
775 | (
776 | az eventgrid event-subscription create \
777 | --source-resource-id $storageAccountId \
778 | --name $subscriptionName \
779 | --endpoint-type webhook \
780 | --endpoint $endpointUrl \
781 | --deadletter-endpoint $storageAccountId/blobServices/default/containers/$deadLetterContainerName 1> /dev/null
782 | )
783 |
784 | if [ $? == 0 ]; then
785 | echo "["$subscriptionName"] Event Grid subscription successfully created"
786 | fi
787 | else
788 | echo "An Event Grid subscription called ["$subscriptionName"] already exists for ["$storageAccountName"] storage account"
789 | fi
790 | ```
791 |
792 | ## Run the sample
793 | You can use the [Azure Storage Explorer](https://azure.microsoft.com/en-us/features/storage-explorer/) to upload files to the container in the storage account monitored by the Event Grid Storage Topic, as shown by the following picture.
794 |
795 | 
796 |
797 | You can use the [Service Bus Explorer](https://github.com/paolosalvatori/ServiceBusExplorer) to see the messages sent by the Azure Function to the queue in the target Service Bus namespace.
798 |
799 | 
800 |
801 | Finally, you can use [Kusto Query Language](https://kusto.azurewebsites.net/docs/) to create a queries in Application Insights to analyze the metrics and logs generated by the Azure Function.
802 |
803 | 
804 |
805 | Here are a couple of sample queries:
806 |
807 | **customMetrics**
808 | ```
809 | let min_t = toscalar(customMetrics| where name in ('ProcessBlobEvents Created', 'ProcessBlobEvents Deleted') | summarize min(timestamp));
810 | let max_t = toscalar(customMetrics| where name in ('ProcessBlobEvents Created', 'ProcessBlobEvents Deleted') | summarize max(timestamp));
811 | customMetrics
812 | | where name in ('ProcessBlobEvents Created', 'ProcessBlobEvents Deleted')
813 | | extend contentType = tostring(customDimensions.ContentType)
814 | | make-series eventCount=count() default=0 on timestamp in range(min_t, max_t, 1s) by contentType
815 | | extend movingAverage=series_fir(eventCount, repeat(1, 5), true, true),
816 | series_fit_2lines(eventCount),
817 | series_fit_line(eventCount)
818 | | render timechart
819 | ```
820 |
821 | **customEvents**
822 | ```
823 | customEvents
824 | | project timestamp, message=name, operation=operation_Name
825 | ```
826 |
--------------------------------------------------------------------------------
/Scripts/create-event-grid-subscription-for-azure-function.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # variables
4 | location="WestEurope"
5 | storageAccountName="babofiles"
6 | storageAccountResourceGroup="BaboFilesResourceGroup"
7 | functionAppName="EventGridBlobEvents"
8 | functionAppResourceGroup="EventGridBlobEventsResourceGroup"
9 | functionName="ProcessBlobEvents"
10 | subscriptionName='BaboFilesAzureFunctionSubscriber'
11 | deadLetterContainerName="deadletter"
12 | filesContainerName="files"
13 | subjectBeginsWith="/blobServices/default/containers/"$filesContainerName
14 |
15 | # functions
16 | function getEventGridExtensionKey
17 | {
18 | # get Kudu username
19 | echo "Retrieving username from ["$functionAppName"] Azure Function publishing profile..."
20 | username=$(az functionapp deployment list-publishing-profiles --name $1 --resource-group $2 --query '[?publishMethod==`MSDeploy`].userName' --output tsv)
21 |
22 | if [ -n $username ]; then
23 | echo "["$username"] username successfully retrieved"
24 | else
25 | echo "No username could be retrieved"
26 | return
27 | fi
28 |
29 | # get Kudu password
30 | echo "Retrieving password from ["$functionAppName"] Azure Function publishing profile..."
31 | password=$(az functionapp deployment list-publishing-profiles --name $1 --resource-group $2 --query '[?publishMethod==`MSDeploy`].userPWD' --output tsv)
32 |
33 | if [ -n $password ]; then
34 | echo "["$password"] password successfully retrieved"
35 | else
36 | echo "No password could be retrieved"
37 | return
38 | fi
39 |
40 | # get jwt
41 | echo "Retrieving JWT token from Azure Function \ Kudu Management API..."
42 | jwt=$(sed -e 's/^"//' -e 's/"$//' <<< $(curl https://$functionAppName.scm.azurewebsites.net/api/functions/admin/token --user $username":"$password --silent))
43 |
44 | if [ -n $jwt ]; then
45 | echo "JWT token successfully retrieved"
46 | else
47 | echo "No JWT token could be retrieved"
48 | return
49 | fi
50 |
51 | # get eventgrid_extension key
52 | echo "Retrieving [eventgrid_extension] key..."
53 | eventGridExtensionKey=$(sed -e 's/^"//' -e 's/"$//' <<< $(curl -H 'Accept: application/json' -H "Authorization: Bearer ${jwt}" https://$functionAppName.azurewebsites.net/admin/host/systemkeys/eventgrid_extension --silent | jq .value))
54 |
55 | if [ -n $eventGridExtensionKey ]; then
56 | echo "[eventgrid_extension] key successfully retrieved"
57 | else
58 | echo "No [eventgrid_extension] key could be retrieved"
59 | return
60 | fi
61 | }
62 |
63 | # check if the storage account exists
64 | echo "Checking if ["$storageAccountName"] storage account actually exists..."
65 |
66 | set +e
67 | (
68 | az storage account show --name $storageAccountName --resource-group $storageAccountResourceGroup &> /dev/null
69 | )
70 |
71 | if [ $? != 0 ]; then
72 | echo "No ["$storageAccountName"] storage account actually exists"
73 | set -e
74 | (
75 | # create the storage account
76 | az storage account create \
77 | --name $storageAccountName \
78 | --resource-group $storageAccountResourceGroup \
79 | --location $location \
80 | --sku Standard_LRS \
81 | --kind BlobStorage \
82 | --access-tier Hot 1> /dev/null
83 | )
84 | echo "["$storageAccountName"] storage account successfully created"
85 | else
86 | echo "["$storageAccountName"] storage account already exists"
87 | fi
88 |
89 | # get storage account connection string
90 | echo "Retrieving the connection string for ["$storageAccountName"] storage account..."
91 | connectionString=$(az storage account show-connection-string --name $storageAccountName --resource-group $storageAccountResourceGroup --query connectionString --output tsv)
92 |
93 | if [ -n $connectionString ]; then
94 | echo "The connection string for ["$storageAccountName"] storage account is ["$connectionString"]"
95 | else
96 | echo "Failed to retrieve the connection string for ["$storageAccountName"] storage account"
97 | return
98 | fi
99 |
100 | # checking if deadletter container exists
101 | echo "Checking if ["$deadLetterContainerName"] container already exists..."
102 | set +e
103 | (
104 | az storage container show --name $deadLetterContainerName --connection-string $connectionString &> /dev/null
105 | )
106 |
107 | if [ $? != 0 ]; then
108 | echo "No ["$deadLetterContainerName"] container actually exists in ["$storageAccountName"] storage account"
109 | set -e
110 | (
111 | # create deadletter container
112 | az storage container create \
113 | --name $deadLetterContainerName \
114 | --public-access off \
115 | --connection-string $connectionString 1> /dev/null
116 | )
117 | echo "["$deadLetterContainerName"] container successfully created in ["$storageAccountName"] storage account"
118 | else
119 | echo "A container called ["$deadLetterContainerName"] already exists in ["$storageAccountName"] storage account"
120 | fi
121 |
122 | # checking if files container exists
123 | echo "Checking if ["$filesContainerName"] container already exists..."
124 | set +e
125 | (
126 | az storage container show --name $filesContainerName --connection-string $connectionString &> /dev/null
127 | )
128 |
129 | if [ $? != 0 ]; then
130 | echo "No ["$filesContainerName"] container actually exists in ["$storageAccountName"] storage account"
131 | set -e
132 | (
133 | # create files container
134 | az storage container create \
135 | --name $filesContainerName \
136 | --public-access off \
137 | --connection-string $connectionString 1> /dev/null
138 | )
139 | echo "["$filesContainerName"] container successfully created in ["$storageAccountName"] storage account"
140 | else
141 | echo "A container called ["$filesContainerName"] already exists in ["$storageAccountName"] storage account"
142 | fi
143 |
144 | # retrieve resource id for the storage account
145 | echo "Retrieving the resource id for ["$storageAccountName"] storage account..."
146 | storageAccountId=$(az storage account show --name $storageAccountName --resource-group $storageAccountResourceGroup --query id --output tsv 2> /dev/null)
147 |
148 | if [ -n $storageAccountId ]; then
149 | echo "Resource id for ["$storageAccountName"] storage account successfully retrieved: ["$storageAccountId"]"
150 | else
151 | echo "Failed to retrieve resource id for ["$storageAccountName"] storage account"
152 | return
153 | fi
154 |
155 | # retrieve eventgrid_extensionkey
156 | getEventGridExtensionKey $functionAppName $functionAppResourceGroup
157 |
158 | if [ -z $eventGridExtensionKey ]; then
159 | echo "Failed to retrieve eventgrid_extensionkey"
160 | return
161 | fi
162 |
163 | # creating the endpoint URL for the Azure Function
164 | endpointUrl="https://$functionAppName.azurewebsites.net/runtime/webhooks/eventgrid?functionName=$functionName&code=$eventGridExtensionKey"
165 |
166 | echo "The endpoint for the ["$functionName"] function in the ["$functionAppName"] function app is ["$endpointUrl"]"
167 |
168 | echo "Checking if Azure CLI eventgrid extension is installed..."
169 | set +e
170 | (
171 | az extension show --name eventgrid --query name --output tsv &> /dev/null
172 | )
173 |
174 | if [ $? != 0 ]; then
175 | echo "The Azure CLI eventgrid extension was not found. Installing the extension..."
176 | az extension add --name eventgrid
177 | else
178 | echo "Azure CLI eventgrid extension successfully found. Updating the extension to the latest version..."
179 | az extension update --name eventgrid
180 | fi
181 |
182 | # checking if the subscription already exists
183 | echo "Checking if ["$subscriptionName"] Event Grid subscription already exists for ["$storageAccountName"] storage account..."
184 | set +e
185 | (
186 | az eventgrid event-subscription show --name $subscriptionName --source-resource-id $storageAccountId &> /dev/null
187 | )
188 |
189 | if [ $? != 0 ]; then
190 | echo "No ["$subscriptionName"] Event Grid subscription actually exists for ["$storageAccountName"] storage account"
191 | echo "Creating a subscription for the ["$endpointUrl"] endpoint of the ["$functionName"] Azure Function..."
192 |
193 | set +e
194 | (
195 | az eventgrid event-subscription create \
196 | --source-resource-id $storageAccountId \
197 | --name $subscriptionName \
198 | --endpoint-type webhook \
199 | --endpoint $endpointUrl \
200 | --subject-begins-with $subjectBeginsWith \
201 | --deadletter-endpoint $storageAccountId/blobServices/default/containers/$deadLetterContainerName 1> /dev/null
202 | )
203 |
204 | if [ $? == 0 ]; then
205 | echo "["$subscriptionName"] Event Grid subscription successfully created"
206 | fi
207 | else
208 | echo "An Event Grid subscription called ["$subscriptionName"] already exists for ["$storageAccountName"] storage account"
209 | fi
210 |
--------------------------------------------------------------------------------
/Scripts/create-event-grid-subscription-for-local-function.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # variables
4 | location="WestEurope"
5 | storageAccountName="babofiles"
6 | storageAccountResourceGroup="BaboFilesResourceGroup"
7 | subscriptionName='BaboFilesLocalDebugging'
8 | ngrockSubdomain="db1abac5"
9 | functionName="ProcessBlobEvents"
10 | endpointUrl="https://"$ngrockSubdomain".ngrok.io/runtime/webhooks/EventGrid?functionName="$functionName
11 | deadLetterContainerName="deadletter"
12 | filesContainerName="files"
13 | subjectBeginsWith="/blobServices/default/containers/"$filesContainerName
14 |
15 | # check if the storage account exists
16 | echo "Checking if ["$storageAccountName"] storage account actually exists..."
17 |
18 | set +e
19 | (
20 | az storage account show --name $storageAccountName --resource-group $storageAccountResourceGroup &> /dev/null
21 | )
22 |
23 | if [ $? != 0 ]; then
24 | echo "No ["$storageAccountName"] storage account actually exists"
25 | set -e
26 | (
27 | # create the storage account
28 | az storage account create \
29 | --name $storageAccountName \
30 | --resource-group $storageAccountResourceGroup \
31 | --location $location \
32 | --sku Standard_LRS \
33 | --kind BlobStorage \
34 | --access-tier Hot 1> /dev/null
35 | )
36 | echo "["$storageAccountName"] storage account successfully created"
37 | else
38 | echo "["$storageAccountName"] storage account already exists"
39 | fi
40 |
41 | # get storage account connection string
42 | echo "Retrieving the connection string for ["$storageAccountName"] storage account..."
43 | connectionString=$(az storage account show-connection-string --name $storageAccountName --resource-group $storageAccountResourceGroup --query connectionString --output tsv)
44 |
45 | if [ -n $connectionString ]; then
46 | echo "The connection string for ["$storageAccountName"] storage account is ["$connectionString"]"
47 | else
48 | echo "Failed to retrieve the connection string for ["$storageAccountName"] storage account"
49 | return
50 | fi
51 |
52 | # checking if deadletter container exists
53 | echo "Checking if ["$deadLetterContainerName"] container already exists..."
54 | set +e
55 | (
56 | az storage container show --name $deadLetterContainerName --connection-string $connectionString &> /dev/null
57 | )
58 |
59 | if [ $? != 0 ]; then
60 | echo "No ["$deadLetterContainerName"] container actually exists in ["$storageAccountName"] storage account"
61 | set -e
62 | (
63 | # create deadletter container
64 | az storage container create \
65 | --name $deadLetterContainerName \
66 | --public-access off \
67 | --connection-string $connectionString 1> /dev/null
68 | )
69 | echo "["$deadLetterContainerName"] container successfully created in ["$storageAccountName"] storage account"
70 | else
71 | echo "A container called ["$deadLetterContainerName"] already exists in ["$storageAccountName"] storage account"
72 | fi
73 |
74 | # checking if files container exists
75 | echo "Checking if ["$filesContainerName"] container already exists..."
76 | set +e
77 | (
78 | az storage container show --name $filesContainerName --connection-string $connectionString &> /dev/null
79 | )
80 |
81 | if [ $? != 0 ]; then
82 | echo "No ["$filesContainerName"] container actually exists in ["$storageAccountName"] storage account"
83 | set -e
84 | (
85 | # create files container
86 | az storage container create \
87 | --name $filesContainerName \
88 | --public-access off \
89 | --connection-string $connectionString 1> /dev/null
90 | )
91 | echo "["$filesContainerName"] container successfully created in ["$storageAccountName"] storage account"
92 | else
93 | echo "A container called ["$filesContainerName"] already exists in ["$storageAccountName"] storage account"
94 | fi
95 |
96 | # retrieve resource id for the storage account
97 | echo "Retrieving the resource id for ["$storageAccountName"] storage account..."
98 | storageAccountId=$(az storage account show --name $storageAccountName --resource-group $storageAccountResourceGroup --query id --output tsv 2> /dev/null)
99 |
100 | if [ -n $storageAccountId ]; then
101 | echo "Resource id for ["$storageAccountName"] storage account successfully retrieved: ["$storageAccountId"]"
102 | else
103 | echo "Failed to retrieve resource id for ["$storageAccountName"] storage account"
104 | return
105 | fi
106 |
107 | echo "Checking if Azure CLI eventgrid extension is installed..."
108 | set +e
109 | (
110 | az extension show --name eventgrid --query name --output tsv &> /dev/null
111 | )
112 |
113 | if [ $? != 0 ]; then
114 | echo "The Azure CLI eventgrid extension was not found. Installing the extension..."
115 | az extension add --name eventgrid
116 | else
117 | echo "Azure CLI eventgrid extension successfully found. Updating the extension to the latest version..."
118 | az extension update --name eventgrid
119 | fi
120 |
121 | # checking if the subscription already exists
122 | echo "Checking if ["$subscriptionName"] Event Grid subscription already exists for ["$storageAccountName"] storage account..."
123 | set +e
124 | (
125 | az eventgrid event-subscription show --name $subscriptionName --source-resource-id $storageAccountId &> /dev/null
126 | )
127 |
128 | if [ $? != 0 ]; then
129 | echo "No ["$subscriptionName"] Event Grid subscription actually exists for ["$storageAccountName"] storage account"
130 | echo "Creating a subscription for the ["$endpointUrl"] ngrock local endpoint..."
131 |
132 | set +e
133 | (
134 | az eventgrid event-subscription create \
135 | --source-resource-id $storageAccountId \
136 | --name $subscriptionName \
137 | --endpoint-type webhook \
138 | --endpoint $endpointUrl \
139 | --subject-begins-with $subjectBeginsWith \
140 | --deadletter-endpoint $storageAccountId/blobServices/default/containers/$deadLetterContainerName 1> /dev/null
141 | )
142 |
143 | if [ $? == 0 ]; then
144 | echo "["$subscriptionName"] Event Grid subscription successfully created"
145 | fi
146 | else
147 | echo "An Event Grid subscription called ["$subscriptionName"] already exists for ["$storageAccountName"] storage account"
148 | fi
--------------------------------------------------------------------------------
/Scripts/create-event-grid-subscription-for-web-app.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # variables
4 | location="WestEurope"
5 | storageAccountName="babofiles"
6 | storageAccountResourceGroup="BaboFilesResourceGroup"
7 | subscriptionName='BaboFilesWebApp'
8 | webAppSubdomain="baboeventgridviewer"
9 | functionName="ProcessBlobEvents"
10 | endpointUrl="https://"$webAppSubdomain".azurewebsites.net/api/updates"
11 | deadLetterContainerName="deadletter"
12 | filesContainerName="files"
13 |
14 | # check if the storage account exists
15 | echo "Checking if ["$storageAccountName"] storage account actually exists..."
16 |
17 | set +e
18 | (
19 | az storage account show --name $storageAccountName --resource-group $storageAccountResourceGroup &> /dev/null
20 | )
21 |
22 | if [ $? != 0 ]; then
23 | echo "No ["$storageAccountName"] storage account actually exists"
24 | set -e
25 | (
26 | # create the storage account
27 | az storage account create \
28 | --name $storageAccountName \
29 | --resource-group $storageAccountResourceGroup \
30 | --location $location \
31 | --sku Standard_LRS \
32 | --kind BlobStorage \
33 | --access-tier Hot 1> /dev/null
34 | )
35 | echo "["$storageAccountName"] storage account successfully created"
36 | else
37 | echo "["$storageAccountName"] storage account already exists"
38 | fi
39 |
40 | # get storage account connection string
41 | echo "Retrieving the connection string for ["$storageAccountName"] storage account..."
42 | connectionString=$(az storage account show-connection-string --name $storageAccountName --resource-group $storageAccountResourceGroup --query connectionString --output tsv)
43 |
44 | if [ -n $connectionString ]; then
45 | echo "The connection string for ["$storageAccountName"] storage account is ["$connectionString"]"
46 | else
47 | echo "Failed to retrieve the connection string for ["$storageAccountName"] storage account"
48 | return
49 | fi
50 |
51 | # checking if deadletter container exists
52 | echo "Checking if ["$deadLetterContainerName"] container already exists..."
53 | set +e
54 | (
55 | az storage container show --name $deadLetterContainerName --connection-string $connectionString &> /dev/null
56 | )
57 |
58 | if [ $? != 0 ]; then
59 | echo "No ["$deadLetterContainerName"] container actually exists in ["$storageAccountName"] storage account"
60 | set -e
61 | (
62 | # create deadletter container
63 | az storage container create \
64 | --name $deadLetterContainerName \
65 | --public-access off \
66 | --connection-string $connectionString 1> /dev/null
67 | )
68 | echo "["$deadLetterContainerName"] container successfully created in ["$storageAccountName"] storage account"
69 | else
70 | echo "A container called ["$deadLetterContainerName"] already exists in ["$storageAccountName"] storage account"
71 | fi
72 |
73 | # checking if files container exists
74 | echo "Checking if ["$filesContainerName"] container already exists..."
75 | set +e
76 | (
77 | az storage container show --name $filesContainerName --connection-string $connectionString &> /dev/null
78 | )
79 |
80 | if [ $? != 0 ]; then
81 | echo "No ["$filesContainerName"] container actually exists in ["$storageAccountName"] storage account"
82 | set -e
83 | (
84 | # create files container
85 | az storage container create \
86 | --name $filesContainerName \
87 | --public-access off \
88 | --connection-string $connectionString 1> /dev/null
89 | )
90 | echo "["$filesContainerName"] container successfully created in ["$storageAccountName"] storage account"
91 | else
92 | echo "A container called ["$filesContainerName"] already exists in ["$storageAccountName"] storage account"
93 | fi
94 |
95 | # retrieve resource id for the storage account
96 | echo "Retrieving the resource id for ["$storageAccountName"] storage account..."
97 | storageAccountId=$(az storage account show --name $storageAccountName --resource-group $storageAccountResourceGroup --query id --output tsv 2> /dev/null)
98 |
99 | if [ -n $storageAccountId ]; then
100 | echo "Resource id for ["$storageAccountName"] storage account successfully retrieved: ["$storageAccountId"]"
101 | else
102 | echo "Failed to retrieve resource id for ["$storageAccountName"] storage account"
103 | return
104 | fi
105 |
106 | echo "Checking if Azure CLI eventgrid extension is installed..."
107 | set +e
108 | (
109 | az extension show --name eventgrid --query name --output tsv &> /dev/null
110 | )
111 |
112 | if [ $? != 0 ]; then
113 | echo "The Azure CLI eventgrid extension was not found. Installing the extension..."
114 | az extension add --name eventgrid
115 | else
116 | echo "Azure CLI eventgrid extension successfully found. Updating the extension to the latest version..."
117 | az extension update --name eventgrid
118 | fi
119 |
120 | # checking if the subscription already exists
121 | echo "Checking if ["$subscriptionName"] Event Grid subscription already exists for ["$storageAccountName"] storage account..."
122 | set +e
123 | (
124 | az eventgrid event-subscription show --name $subscriptionName --source-resource-id $storageAccountId &> /dev/null
125 | )
126 |
127 | if [ $? != 0 ]; then
128 | echo "No ["$subscriptionName"] Event Grid subscription actually exists for ["$storageAccountName"] storage account"
129 | echo "Creating the subscription for the ["$endpointUrl"] endpoint of the Azure Event Grid Viewer web app..."
130 | set +e
131 | (
132 | az eventgrid event-subscription create \
133 | --source-resource-id $storageAccountId \
134 | --name $subscriptionName \
135 | --endpoint-type webhook \
136 | --endpoint $endpointUrl \
137 | --deadletter-endpoint $storageAccountId/blobServices/default/containers/$deadLetterContainerName 1> /dev/null
138 | )
139 |
140 | if [ $? == 0 ]; then
141 | echo "["$subscriptionName"] Event Grid subscription successfully created"
142 | fi
143 | else
144 | echo "An Event Grid subscription called ["$subscriptionName"] already exists for ["$storageAccountName"] storage account"
145 | fi
--------------------------------------------------------------------------------