diff --git a/.github/workflows/publish-api-image.yaml b/.github/workflows/publish-api-image.yaml index 2842f58..23e9d8e 100644 --- a/.github/workflows/publish-api-image.yaml +++ b/.github/workflows/publish-api-image.yaml @@ -24,7 +24,7 @@ jobs: # Step 4: Build the Docker image - name: Build the Docker image - run: docker build -t "${{ secrets.ACR_SERVER }}/aquaapi:latest" ./api/AquaApi + run: docker build -t "${{ secrets.ACR_SERVER }}/aquaapi:latest" ./src/AquaApi # Step 5: Push the Docker image to Azure Container Registry - name: Push the Docker image diff --git a/.github/workflows/publish-data-generation-function.yaml b/.github/workflows/publish-data-generation-function.yaml new file mode 100644 index 0000000..87c7978 --- /dev/null +++ b/.github/workflows/publish-data-generation-function.yaml @@ -0,0 +1,36 @@ +name: Deploy DotNet project to Azure Function App + +on: + [push] + +env: + AZURE_FUNCTIONAPP_NAME: 'dev-aquaplatform-func' # set this to your function app name on Azure + AZURE_FUNCTIONAPP_PACKAGE_PATH: 'src/DataGeneratorFunction' # set this to the path to your function app project, defaults to the repository root + DOTNET_VERSION: '8.0.x' # set this to the dotnet version to use (e.g. '2.1.x', '3.1.x', '5.0.x') + +jobs: + build-and-deploy: + runs-on: windows-latest + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@v3 + + - name: Setup DotNet ${{ env.DOTNET_VERSION }} Environment + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: 'Resolve Project Dependencies Using Dotnet' + shell: pwsh + run: | + pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}' + dotnet publish --configuration Release --output ./output + popd + + - name: 'Run Azure Functions Action' + uses: Azure/functions-action@v1 + id: fa + with: + app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }} + package: '${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/output' + publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }} \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..bb76300 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "ms-azuretools.vscode-azurefunctions", + "ms-dotnettools.csharp" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..894cbe6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to .NET Functions", + "type": "coreclr", + "request": "attach", + "processId": "${command:azureFunctions.pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..54b2105 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "azureFunctions.deploySubpath": "src/DataGeneratorFunction/bin/Release/net8.0/publish", + "azureFunctions.projectLanguage": "C#", + "azureFunctions.projectRuntime": "~4", + "debug.internalConsoleOptions": "neverOpen", + "azureFunctions.preDeployTask": "publish (functions)", + "azureFunctions.projectSubpath": "src/DataGeneratorFunction" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..7734ecb --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,81 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "clean (functions)", + "command": "dotnet", + "args": [ + "clean", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/src/DataGeneratorFunction" + } + }, + { + "label": "build (functions)", + "command": "dotnet", + "args": [ + "build", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "dependsOn": "clean (functions)", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/src/DataGeneratorFunction" + } + }, + { + "label": "clean release (functions)", + "command": "dotnet", + "args": [ + "clean", + "--configuration", + "Release", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/src/DataGeneratorFunction" + } + }, + { + "label": "publish (functions)", + "command": "dotnet", + "args": [ + "publish", + "--configuration", + "Release", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "dependsOn": "clean release (functions)", + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/src/DataGeneratorFunction" + } + }, + { + "type": "func", + "dependsOn": "build (functions)", + "options": { + "cwd": "${workspaceFolder}/src/DataGeneratorFunction" + }, + "command": "host start", + "isBackground": true, + "problemMatcher": "$func-dotnet-watch" + } + ] +} \ No newline at end of file diff --git a/dataplatform-aquaculture-template.sln b/dataplatform-aquaculture-template.sln index 5bf1763..97a67d6 100644 --- a/dataplatform-aquaculture-template.sln +++ b/dataplatform-aquaculture-template.sln @@ -1,30 +1,32 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.002.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "api", "api", "{A6ED9EDF-D599-47B8-A6B2-2844E3FEBF9C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AquaApi", "src\AquaApi\AquaApi.csproj", "{35950192-252F-44A8-B710-63E470A67118}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AquaApi", "api\AquaApi\AquaApi.csproj", "{35950192-252F-44A8-B710-63E470A67118}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataGeneratorFunction", "src\DataGeneratorFunction\DataGeneratorFunction.csproj", "{3FFDA260-76E7-4E96-9F97-5B80E30FF2F2}" EndProject Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {35950192-252F-44A8-B710-63E470A67118}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {35950192-252F-44A8-B710-63E470A67118}.Debug|Any CPU.Build.0 = Debug|Any CPU - {35950192-252F-44A8-B710-63E470A67118}.Release|Any CPU.ActiveCfg = Release|Any CPU - {35950192-252F-44A8-B710-63E470A67118}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {35950192-252F-44A8-B710-63E470A67118} = {A6ED9EDF-D599-47B8-A6B2-2844E3FEBF9C} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {00A64A12-39CC-40EA-996D-79EA6539A35B} - EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {35950192-252F-44A8-B710-63E470A67118}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35950192-252F-44A8-B710-63E470A67118}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35950192-252F-44A8-B710-63E470A67118}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35950192-252F-44A8-B710-63E470A67118}.Release|Any CPU.Build.0 = Release|Any CPU + {3FFDA260-76E7-4E96-9F97-5B80E30FF2F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FFDA260-76E7-4E96-9F97-5B80E30FF2F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FFDA260-76E7-4E96-9F97-5B80E30FF2F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FFDA260-76E7-4E96-9F97-5B80E30FF2F2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {00A64A12-39CC-40EA-996D-79EA6539A35B} + EndGlobalSection EndGlobal diff --git a/api/AquaApi/AquaApi.csproj b/src/AquaApi/AquaApi.csproj similarity index 100% rename from api/AquaApi/AquaApi.csproj rename to src/AquaApi/AquaApi.csproj diff --git a/api/AquaApi/Dockerfile b/src/AquaApi/Dockerfile similarity index 100% rename from api/AquaApi/Dockerfile rename to src/AquaApi/Dockerfile diff --git a/api/AquaApi/MeasurementRow.cs b/src/AquaApi/MeasurementRow.cs similarity index 100% rename from api/AquaApi/MeasurementRow.cs rename to src/AquaApi/MeasurementRow.cs diff --git a/api/AquaApi/Program.cs b/src/AquaApi/Program.cs similarity index 100% rename from api/AquaApi/Program.cs rename to src/AquaApi/Program.cs diff --git a/api/AquaApi/Properties/launchSettings.json b/src/AquaApi/Properties/launchSettings.json similarity index 100% rename from api/AquaApi/Properties/launchSettings.json rename to src/AquaApi/Properties/launchSettings.json diff --git a/api/AquaApi/appsettings.json b/src/AquaApi/appsettings.json similarity index 100% rename from api/AquaApi/appsettings.json rename to src/AquaApi/appsettings.json diff --git a/src/DataGeneratorFunction/.gitignore b/src/DataGeneratorFunction/.gitignore new file mode 100644 index 0000000..e9b4f59 --- /dev/null +++ b/src/DataGeneratorFunction/.gitignore @@ -0,0 +1,264 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Azure Functions localsettings file +local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/src/DataGeneratorFunction/DataGeneratorFunction.cs b/src/DataGeneratorFunction/DataGeneratorFunction.cs new file mode 100644 index 0000000..550d178 --- /dev/null +++ b/src/DataGeneratorFunction/DataGeneratorFunction.cs @@ -0,0 +1,44 @@ +using System; +using System.Text.Json; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; + +namespace DataGeneratorFunction +{ + public class DataGeneratorFunction + { + private readonly ILogger _logger; + + public DataGeneratorFunction(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + [Function("DataGeneratorFunction")] + [EventHubOutput("dev-aquaplatform-eventhub", Connection = "EventHubConnectionString")] + public string Run([TimerTrigger("0 */1 * * * *")] TimerInfo myTimer) + { + _logger.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}"); + + if (myTimer.ScheduleStatus is not null) + { + _logger.LogInformation($"Next timer schedule at: {myTimer.ScheduleStatus.Next}"); + } + + // Generate random data + var randomData = new + { + Id = Guid.NewGuid(), + Timestamp = DateTime.UtcNow, + Value = new Random().Next(1, 100) + }; + + // Convert data to JSON + var eventHubMessage = JsonSerializer.Serialize(randomData); + + // Log generated data + _logger.LogInformation($"Generated data: {eventHubMessage}"); + return eventHubMessage; + } + } +} diff --git a/src/DataGeneratorFunction/DataGeneratorFunction.csproj b/src/DataGeneratorFunction/DataGeneratorFunction.csproj new file mode 100644 index 0000000..399d4e4 --- /dev/null +++ b/src/DataGeneratorFunction/DataGeneratorFunction.csproj @@ -0,0 +1,33 @@ + + + net8.0 + v4 + Exe + enable + enable + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + + + + \ No newline at end of file diff --git a/src/DataGeneratorFunction/Program.cs b/src/DataGeneratorFunction/Program.cs new file mode 100644 index 0000000..4e74c98 --- /dev/null +++ b/src/DataGeneratorFunction/Program.cs @@ -0,0 +1,15 @@ +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; + +var host = new HostBuilder() + .ConfigureFunctionsWebApplication( + ) + .ConfigureServices(services => + { + services.AddApplicationInsightsTelemetryWorkerService(); // Optional: App Insights + services.ConfigureFunctionsApplicationInsights(); // Optional: App Insights config + }) + .Build(); + +host.Run(); \ No newline at end of file diff --git a/src/DataGeneratorFunction/Properties/launchSettings.json b/src/DataGeneratorFunction/Properties/launchSettings.json new file mode 100644 index 0000000..e194d35 --- /dev/null +++ b/src/DataGeneratorFunction/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "DataGeneratorFunction": { + "commandName": "Project", + "commandLineArgs": "--port 7259", + "launchBrowser": false + } + } +} \ No newline at end of file diff --git a/src/DataGeneratorFunction/host.json b/src/DataGeneratorFunction/host.json new file mode 100644 index 0000000..ee5cf5f --- /dev/null +++ b/src/DataGeneratorFunction/host.json @@ -0,0 +1,12 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + }, + "enableLiveMetricsFilters": true + } + } +} \ No newline at end of file diff --git a/src/DataGeneratorFunction/readme.md b/src/DataGeneratorFunction/readme.md new file mode 100644 index 0000000..0b247b5 --- /dev/null +++ b/src/DataGeneratorFunction/readme.md @@ -0,0 +1,11 @@ +# TimerTrigger - C# + +The `TimerTrigger` makes it incredibly easy to have your functions executed on a schedule. This sample demonstrates a simple use case of calling your function every 5 minutes. + +## How it works + +For a `TimerTrigger` to work, you provide a schedule in the form of a [cron expression](https://en.wikipedia.org/wiki/Cron#CRON_expression)(See the link for full details). A cron expression is a string with 6 separate expressions which represent a given schedule via patterns. The pattern we use to represent every 5 minutes is `0 */5 * * * *`. This, in plain text, means: "When seconds is equal to 0, minutes is divisible by 5, for any hour, day of the month, month, day of the week, or year". + +## Learn more + + Documentation \ No newline at end of file diff --git a/terraform/application_insights.tf b/terraform/application_insights.tf new file mode 100644 index 0000000..4e88cca --- /dev/null +++ b/terraform/application_insights.tf @@ -0,0 +1,16 @@ +resource "azurerm_application_insights" "apin" { + name = "dp-appinsights" + location = azurerm_resource_group.data_platform.location + resource_group_name = azurerm_resource_group.data_platform.name + application_type = "web" + workspace_id = azurerm_log_analytics_workspace.log.id + +} + +resource "azurerm_log_analytics_workspace" "log" { + name = "workspace-test" + location = azurerm_resource_group.data_platform.location + resource_group_name = azurerm_resource_group.data_platform.name + sku = "PerGB2018" + retention_in_days = 30 +} \ No newline at end of file diff --git a/terraform/event_hub.tf b/terraform/event_hub.tf new file mode 100644 index 0000000..07aa5b3 --- /dev/null +++ b/terraform/event_hub.tf @@ -0,0 +1,25 @@ + +resource "azurerm_eventhub_namespace" "this" { + name = "dev-aquaplatform-ehn" + location = azurerm_resource_group.data_platform.location + resource_group_name = azurerm_resource_group.data_platform.name + sku = "Standard" + capacity = 1 +} + +resource "azurerm_eventhub" "this" { + name = "dev-aquaplatform-eventhub" + namespace_name = azurerm_eventhub_namespace.this.name + resource_group_name = azurerm_resource_group.data_platform.name + partition_count = 2 + message_retention = 1 +} + +resource "azurerm_eventhub_namespace_authorization_rule" "this" { + name = "function-authorize-rule" + namespace_name = azurerm_eventhub_namespace.this.name + resource_group_name = azurerm_resource_group.data_platform.name + listen = false + send = true + manage = false +} diff --git a/terraform/function.tf b/terraform/function.tf new file mode 100644 index 0000000..ed71890 --- /dev/null +++ b/terraform/function.tf @@ -0,0 +1,32 @@ +# App Service Plan +resource "azurerm_service_plan" "func" { + name = "dp-func-appserviceplan" + location = azurerm_resource_group.data_platform.location + resource_group_name = azurerm_resource_group.data_platform.name + os_type = "Linux" + sku_name = "B1" +} + +# Function App +resource "azurerm_linux_function_app" "func" { + name = "dev-aquaplatform-func" + location = azurerm_resource_group.data_platform.location + resource_group_name = azurerm_resource_group.data_platform.name + storage_account_name = azurerm_storage_account.data_lake.name + storage_account_access_key = azurerm_storage_account.data_lake.primary_access_key + service_plan_id = azurerm_service_plan.func.id + site_config { + application_insights_connection_string = azurerm_application_insights.apin.connection_string + always_on = true + + application_stack { + dotnet_version = "8.0" + use_dotnet_isolated_runtime = true + } + } + app_settings = { + "EventHubConnectionString" = azurerm_eventhub_namespace_authorization_rule.this.primary_connection_string + } +} + +